CMake: Can subdirectories inherit compile features? - c++

I have a C++ project that consists of a main program (main.cpp), a header that defines an abstract class (algorithm.hpp), and a subdirectory (algorithms/) full of classes that implement the abstract class. I've configured CMake to build the subdirectory as an object library:
# algorithms/CMakeLists.txt
add_library(algorithms_lib OBJECT
algo1.cpp
algo2.cpp
# etc.
)
In my project's root directory, I use this object library as one of the application's sources:
# Top-level CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(MyApp CXX)
add_subdirectory(algorithms)
add_executable(my-app
main.cpp
$<TARGET_OBJECTS:algorithms_lib>
)
My application is written in C++14, so I need to tell CMake to use the correct compile options for that. I don't want to require CMake version 3.8 (since it's not packaged in some recent Linux distros that I want to support), so I can't use the cxx_std_14 compile feature; instead, I've listed a bunch of individual language features that I use:
# Top-level CMakeLists.txt (cont.)
target_compile_features(my-app PUBLIC
cxx_auto_type
cxx_constexpr
cxx_defaulted_functions
# ...14 more lines...
)
The problem is, these features only apply to the top-level my-app target, not the algorithms_lib target, so the sources in the subdirectory don't get compiled as C++14.
I know I could copy the whole big target_compile_features block into algorithms/CMakeLists.txt, but I'd rather not do that — especially since this example is simplified and I actually have nine such subdirectories, each building its own object library. That'd be a lot of duplication of boilerplate code.
Is there a way to set compile features globally for all C++ targets in the project, including subdirectories? Or would it be better to get rid of add_subdirectory and the object library, and just list all the individual subdirectory files in the top-level add_executable command? I'm new to CMake, so I don't know what the "best practices" are for this sort of thing.

It sounds like you want to put the following at the top of your CMakeLists.txt file:
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
These set defaults for the same-named target properties without the leading CMAKE_. The equivalent target properties do the following:
CXX_STANDARD yy sets the desired minimum C++ standard your code wants to use.
CXX_STANDARD_REQUIRED bb says whether CXX_STANDARD is a requirement (bb is YES or some other boolean equivalent) or whether it is just desirable (bb is NO or equivalent).
CXX_EXTENSIONS bb enables (bb = YES) or disables (bb = NO) compiler extensions.
You don't need to specify individual compiler features to achieve what you're ultimately trying to do (enable C++14 for your targets). You can find a full discussion of this whole area here.

Related

What's a simple straightforward find_package set of files for CMake

I'm new(ish) to CMake (meaning off and on over a few years I've been forced to use it even though it's something that's made me question my career path).
I have a folder with an include folder, and a lib folder containing .lib files and their corresponding .dlls for a dependency I'll call "mydep". I need to provide the infrastructure files, mydep-config.cmake, mydep-target.cmake, etc, which will add the includes folders and .lib files to the command line for compiling and linking and then move the .dlls to a specific location.
Can anyone point me to a simple example anywhere on the net that illustrates the way to do this? The CMake documentation is utterly useless.
Thanks!
There are basically 2 files you need to put in the correct location on the file system. Let's assume the library is to import is called ExternLib with version 1.2.3 and compiled for 64 bit. With the and you've got it stored in your repo as
external_dependencies/lib/ExternLib.lib
external_dependencies/bin/ExternLib.dll
external_dependencies/include/ExternLib/ExternHeader.hpp
external_dependencies/include/ExternLib/...
...
and you want the include paths used in your project to be
#include "ExternLib/ExternHeader.hpp"
First of all we'll use the file names externlib-config.cmake and externlib-version-config.cmake to allow the user to use arbitrary case when using find_package.
externlib-version-config.cmake
This file is used to check, if the version of the config script is compatible with the cmake configuration trying to find the package. Adding this file allows you to place multiple versions of the library to be found for different target architectures (e.g. Windows x64, Win32, linux x86_64, ...). find_package won't read the externlib-config.cmake in the same directory, if the version is marked as non-compatible via the version file.
set(PACKAGE_VERSION "1.2.3")
# basically ignore the version passed
set(PACKAGE_VERSION_COMPATIBLE TRUE) # consider every version compatible
#usually you'd add logic for ignoring any unspecified parts of the version here instead of treating unspecified version parts as 0
# we'll keep it simple though
if(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION)
set(PACKAGE_VERSION_EXACT TRUE) # make version as exact match
endif()
if(NOT WIN32)
set(PACKAGE_VERSION_COMPATIBLE FALSE) # disallow older version
else()
if (NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") # note: we'll ignore the possibility of find_package before the first project() command
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") # check for 64 bit
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
externlib-config.cmake
Here we'll put the creation of the actual library in addition to providing any functions/macros/variables the user should have access to. We'll use ExternLib::ExternLib as target name, but you can simply replace this by any name of your choosing unique in your project.
if(NOT TARGET ExternLib::ExternLib) # prevent recreation on multiple uses of find_package
add_library(ExternLib::ExternLib SHARED IMPORTED)
get_filename_component(EXTERNLIB_BASE_DIR ${CMAKE_CURRENT_LISTS_DIR}/../../.. ABSOLUTE)
set(EXTERNLIB_BINARY_DIR ${EXTERNLIB_BASE_DIR} CACHE INTERNAL "Directory containing the dlls for ExternLib")
# add info about locations of the dll/lib files
set_target_properties(ExternLib::ExternLib PROPERTIES
IMPORTED_LOCATION "$CACHE{EXTERNLIB_BINARY_DIR}/ExternLib.dll"
IMPORTED_IMPLIB "${EXTERNLIB_BASE_DIR}/lib/ExternLib.lib"
)
# add include directory information
target_include_directories(ExternLib::ExternLib INTERFACE "${EXTERNLIB_BASE_DIR}/include")
# add dependencies, if required; you may need to use find_package to locate those
# target_link_libraries(ExternLib::ExternLib INTERFACE ...)
endif()
# any helper function/macro definitions should go here, since they may need to get reintroduced
Note: I did ignore the possibility of components of a package being specified here. Any components specifying components for find_package are simply ignored for the script above.
Placement of the files
You'll need to place the files in one of some possible paths below one of the directories mentioned in CMAKE_PREFIX_PATH or in one of the default dirs, see find_package Config Mode Search Procedure.
Note: we'll use a path that isn't documented for windows, since this path would also work for linux. (Boost is doing this too.)
lib/cmake/ExternLib-x64-1.2.3
(You could choose a different suffix to lib/cmake/ExternLib or just use lib/cmake/ExternLib. The search procedure picks up on any directory names which starts with the package name ignoring case, if it expects the lib name.)
Place both the files in this directory. externlib-config.cmake assumes lib is external_dependencies/lib here. Otherwise you may need to adjust EXTERNLIB_BASE_DIR accordingly.
Usage
We'll assume the CMakeLists.txt file is placed in the same directory as external_dependencies
project(...)
...
add_executable(my_exe ...)
...
# this allows the user to pass directories to be searched first via -D option during configuration
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR}/external_dependencies)
find_package(ExternLib REQUIRED)
target_link_libraries(my_exe PRIVATE ExternLib::ExternLib)
# allow vs debugger to find the dll without copying the dlls around
set_target_properties(my_exe PROPERTIES
VS_DEBUGGER_ENVIRONMENT "PATH=${EXTERNLIB_BINARY_DIR};$ENV{PATH}"
)

How do I build a parameterized third-party library in cmake?

I have a project in which I have a third party library checked out as a git submodule. The third party library is just source code with no build system. In addition, The third party library must configured on a per-executable basis by way of compiler definitions and selectively compiling only the parts of the library that I need. I use this library in a lot of different repositories, so I want to make a reusable component to generate an instantiation of the library for any particular executable.
The way I've currently attempted this is by creating a generate_thirdparty.cmake. This file looks something like this:
function(generate_thirdparty parameters)
# several calls to add_library for the different components of this third party library
# the generated libraries depend on the parameters argument
# the parameters configure compile definitions and which source files are compiled
endfunction()
Then in my project CMakeLists.txt I have something like:
cmake_minimum_required(VERSION 3.8)
project(test C)
include(generate_thirdparty.cmake)
set(parameters <some parameters here>)
generate_thirdparty(${parameters})
add_executable(my_exe main.c)
target_link_libraries(my_exe <library names from generate_thirdparty>)
It seems like what I have works, but I'm confused on how you're supposed to do this. I've read through other posts and seen people suggest using find_package or ExternalProject_add. Given a third party repository that contains source code and no build system, which you have no control over, how do you create a reusable way to build that library, especially in the case that the library must be parameterized any given executable?
EDIT: I would like to have the flexibility to have multiple instantiations of the library in the same project.
Let's sum up:
The third-party library does not provide its own build.
You need many instantiations of the library within a single build.
You use these instantiations across multiple different repositories.
I think you're pretty much taking the right approach. Let's call the third-party library libFoo for brevity. Here's what I think you should do...
Create a wrapper repository for libFoo that contains a FindFoo.cmake file and the actual foo repository submodule next to it. This is to avoid the contents of FindFoo.cmake from being independently versioned across your various projects.
Include the wrapper as your submodule in dependent projects, say in the directory third_party/foo_wrapper
In those dependent projects, write:
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/third_party/foo_wrapper")
find_package(Foo REQUIRED)
generate_foo(config1 ...)
generate_foo(config2 ...)
add_executable(app1 src/app1/main.cpp)
target_link_libraries(app1 PRIVATE foo::config1)
add_executable(app2 src/app2/main.cpp)
target_link_libraries(app2 PRIVATE foo::config2)
The contents of FindFoo.cmake will simply be:
cmake_minimum_required(VERSION 3.18)
function(generate_foo target)
# Use CMAKE_CURRENT_FUNCTION_LIST_DIR to reference foo's source files
# for example:
set(sources "src/src1.cpp" "src/src2.cpp" ...)
list(TRANSFORM sources PREPEND "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/foo/")
add_library(${target} ${sources})
add_library(foo::${target} ALIAS ${target})
# Do other things with ARGN
endfunction()
# Define a version for this dependency + script combo.
set(Foo_VERSION 0.1.0)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Foo VERSION_VAR Foo_VERSION
HANDLE_COMPONENTS)
The CMAKE_CURRENT_FUNCTION_LIST_DIR is the absolute path to the file containing the function being called. Remember that this is the root of your wrapper repository, so the actual sources for libFoo will be in the adjacent foo directory. list(TRANSFORM) lets us write relative paths in the sources list that get converted to absolute paths for the sake of add_library (passing a relative path to add_library would be relative to the caller's source directory).
I also create an ALIAS target so that callers of generate_foo can link to the alias. This is important because CMake considers names that contain :: to be targets when a library is expected. This very helpfully turns typos from unintended linker flags into configure-time "target not found" errors.
Then we define the function like normal and call the find_package_handle_standard_args to handle find_package arguments like REQUIRED, COMPONENTS (even just to check that none were incorrectly specified), and VERSION. See the docs for it, here: https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html

Cmake Replacing add_compile_definitions with target_compile_definitions [duplicate]

I've been told it's bad practice to do things like seting CFLAGS directly in CMake, and that, instead, I should use the target_compile_definitions() command.
Ok, but - what if I want to use similar/identical definitions for multiple (independent) targets? I don't want to repeat myself over and over again.
I see three possible ways:
The preferred one using target_compile_definitions(... INTERFACE/PUBLIC ...) which would self-propagate the compiler definitions to targets depending on it via target_link_libraries() command.
Using the set_property(TARGET target1 target2 ... APPEND PROPERTY COMPILE_DEFINITIONS ...) to set the same definitions to multiple targets.
You may still use the "old commands" of add_definitions() and remove_definitions() to modify COMPILE_DEFINITIONS directory property (which would pre-set all COMPILE_DEFINITIONS target properties in this directories scope).
References
Is Cmake set variable recursive?
CMake: Is there a difference between set_property(TARGET ...) and set_target_properties?
tl;dr: You can iterate the targets in a loop.
If you have a bunch of targets with some common/similar features, you may want to simply manipulate them all in a loop! Remember - CMake is not like GNU Make, it's a full-fledged scripting language (well, sort of). So you could write:
set(my_targets
foo
bar
baz)
foreach(TARGET ${my_targets})
add_executable(${TARGET} "${TARGET}.cu")
target_compile_options(${TARGET} PRIVATE "--some_option=some_value")
target_link_libraries(${TARGET} PRIVATE some_lib)
# and so on
set_target_properties(
${TARGET}
PROPERTIES
C_STANDARD 99
C_STANDARD_REQUIRED YES
C_EXTENSIONS NO )
endforeach(TARGET)
And you could also initialize an empty list of targets, then add to it here-and-there, and only finally apply your common options and settings to all of them, centrally.
Note: In this example I added PRIVATE compile options, but if you need some of them to propagate to targets using your targets, you can make them PUBLIC).
another neat solution is to define an interface library target (a fake target that does not produce any binaries) with all required properties and compiler definitions, then link the other existing targets against it
example:
add_library(myfakelib INTERFACE)
target_compile_definitions(myfakelib INTERFACE MY_NEEDED_DEFINITION)
add_executable(actualtarget1 main1.cpp)
add_executable(actualtarget2 main2.cpp)
set_property(
TARGET actualtarget1 actualtarget2
APPEND PROPERTY LINK_LIBRARIES myfakelib
)
refs:
https://cmake.org/cmake/help/latest/command/add_library.html#interface-libraries

preferred cmake project structure

I would like to have the following structure A -> B -> C, where:
C is boilerplate code, wrappers for third-party libraries, very
basic code etc.
B is the common classes, functions and data
structures specific to the project's domain.
A is the project itself.
I would like to make it easy to reuse C or B(+C) in future in my other projects. In addition, I have the following requirements:
As all three projects are in-progress, I would like to have an ability to build C, C+B and C+B+A in one shot.
I would prefer the static linkage over dynamic, so that C and C+B would be static libraries, and C+B+A would be the executable
I would like to keep cmake lists and config files simple and clean. Examples which I found in the official wiki and over the internet are pretty big and monstrous.
It would be great if it won't require changing more than a couple of lines if I'd change the locations of A, B or C in the filesystem.
All these three components are using google-test, but I'm not sure if it is important for the project layout.
I am pretty new to cmake and I don't even understand is it better to write XXXConfig.cmake or FindXXX.cmake files. Also, I am not sure, how should I pass relative paths from subcomponent to the parent component using X_INCLUDE_DIRS.
First I have to admit that I agree with #Tsyvarev. Your CMake environment should fit to your processes/workflow and should take project sizes and team structure into account. Or generally speaking the environment CMake will be used in. And this tends to be - in a positive way - very alive.
So this part of your question is difficult to answer and I'll concentrate on the technical part:
CMake has to know the location of the dependencies - relative or absolute - by
having a monolithic source tree (the one you don't want anymore)
CMake share library with multiple executables
CMake: How to setup Source, Library and CMakeLists.txt dependencies?
a common directory location for includes/libraries/binaries
Custom Directory for CMake Library Output
cmake install not installing libraries on windows
getting the paths via config files/variable definitions
How can I get cmake to find my alternative boost installation?
How to add_custom_command() for the CMake build process itself?
using registration in or installation from a database provided on the host
Making cmake library accessible by other cmake packages automatically
cmake wont run build_command in ExternalProject_Add correctly
To keep your CMake files as simple as possible I would recommend to group your CMake code into separate dedicated files:
Prefer toolchain files over if(SomeCompiler) statements
Move common/repeating code parts as function() bodies into a shared CMake include file
Move complex non-target specific code parts into their own (CMake) script files
Example Code
Since you have specifically asked for the find_package() variant, taking Use CMake-enabled libraries in your CMake project and the things listed above:
MyCommonCode.cmake
cmake_policy(SET CMP0022 NEW)
function(my_export_target _target _include_dir)
file(
WRITE "${CMAKE_CURRENT_BINARY_DIR}/${_target}Config.cmake"
"
include(\"\$\{CMAKE_CURRENT_LIST_DIR\}/${_target}Targets.cmake\")
set_property(
TARGET ${_target}
APPEND PROPERTY
INTERFACE_INCLUDE_DIRECTORIES \"${_include_dir}\"
)
"
)
export(
TARGETS ${_target}
FILE "${CMAKE_CURRENT_BINARY_DIR}/${_target}Targets.cmake"
EXPORT_LINK_INTERFACE_LIBRARIES
)
export(PACKAGE ${_target})
endfunction(my_export_target)
C/CMakeLists.txt
include(MyCommonCode.cmake)
...
my_export_target(C "${CMAKE_CURRENT_SOURCE_DIR}/include")
B/CMakeLists.txt
include(MyCommonCode.cmake)
find_package(C REQUIRED)
...
target_link_libraries(B C)
my_export_target(B "${CMAKE_CURRENT_SOURCE_DIR}/include")
A/CMakeLists.txt
include(MyCommonCode.cmake)
find_package(B REQUIRED)
...
target_link_libraries(A B)
This keeps all 3 build environments separate, only sharing the relatively static MyCommonCode.cmake file. So in this approach I have so far not covered your first point, but would recommend the use of a external script to chain/trigger your build steps for A/B/C.

Forcing C99 in CMake (to use 'for' loop initial declaration)

I've been searching a portable way to force CMake to enable the compiler's C99 features in order to avoid the following gcc error for instance:
error: ‘for’ loop initial declarations are only allowed in C99 mode
for (int s = 1; s <= in_para->StepNumber; s++){
^
I also wouldn't like to check for which compiler and append something like:
set(CMAKE_C_FLAGS "-std=c99") # that would be bad
So I found this post: Enabling C99 in CMake and the associated feature request: 0012300: CMake has no cross-platform way to ask for C99. In this Mantis bug I learned about target_compiler_features and after that I found these SOF answers on it: How to activate C++11 in CMake? and How to detect C++11 support of a compiler with CMake.
So my questions are: this target_compiler_features will provide a way to require a C feature as well as a C++ one? What is the most portable way to achive this by now - I'm currently using CMake 2.8.12.2. The target_compiler_features isn't in CMake's most recent release version (3.0.0). Do you know when it is being released?
After creating a target such as a library or executable, put a line like this in your CMakeLists.txt file:
set_property(TARGET tgt PROPERTY C_STANDARD 99)
where tgt is the name of your target.
I think this was added in CMake 3.1, and the documentation is here:
http://www.cmake.org/cmake/help/latest/prop_tgt/C_STANDARD.html
If you need to support versions of CMake older than 3.1, you can use this macro:
macro(use_c99)
if (CMAKE_VERSION VERSION_LESS "3.1")
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
set (CMAKE_C_FLAGS "-std=gnu99 ${CMAKE_C_FLAGS}")
endif ()
else ()
set (CMAKE_C_STANDARD 99)
endif ()
endmacro(use_c99)
After putting that macro in your top-level file so it is visible everywhere, you can just write use_c99() at the top of any CMakeLists file that defines a target with C99 code in it.
CMake issue #15943 for clang users targeting macOS
If you are using CMake and clang to target MacOS there is a bug that can cause the CMAKE_C_STANDARD feature to simply not work (not add any compiler flags). Make sure that you do one of the following things:
Use cmake_minimum_required to require CMake 3.0 or later, or
Set policy CMP0025 to NEW with the following code at the top of your CMakeLists.txt file before the project command:
# Fix behavior of CMAKE_C_STANDARD when targeting macOS.
if (POLICY CMP0025)
cmake_policy(SET CMP0025 NEW)
endif ()
As this question keeps getting attention I'm summarizing here what I think are the best options today.
The following command sets C99 as a minimum requirement for target:
target_compile_features(target PUBLIC c_std_99)
I consider this the preferred way, as it is per target and exposes a way to control the visibility through the PUBLIC, INTERFACE and PRIVATE keywords - see the reference. Although the target_compile_features command was introduced on the 3.1 version, c_std_99 requires at least CMake 3.8.
Similar to the above, another way to set C99 as the standard for target is the following:
set_property(TARGET target PROPERTY C_STANDARD 99)
This is available since CMake 3.1. A possible drawback is that it doesn't enforce the standard (see the reference). For this reason setting the C_STANDARD_REQUIRED property may be useful:
set_property(TARGET target PROPERTY C_STANDARD_REQUIRED ON)
The above two properties are defaulted to the values of CMAKE_C_STANDARD and CMAKE_C_STANDARD_REQUIRED respectively.
So a possible way to make C99 default for all targets is:
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED TRUE)
As a side note, expanding on the target_compile_features approach, there may be no need to require some specific language standard if all you care about is some specific feature. For instance by setting:
target_compile_features(target PUBLIC c_variadic_macros)
CMake will take care to pick the proper flags that enforce the availability of variadic macros. However currently there are only a few such features available for the C language - see CMAKE_C_KNOWN_FEATURES for the complete list - and loop initial declarations is not among them.
In libevent, add the following in CMakeLists.txt
set (CMAKE_C_FLAGS "-std=gnu99 ${CMAKE_C_FLAGS}")
Edit CMakeLists
add on line > 2
set (CMAKE_C_STANDARD 99)
then
cmake 3 ..
The C_STANDARD property will allow use to apply the C standard to a specific target, rather than globally. The following will apply C99 to mytarget.
set_property(TARGET mytarget PROPERTY C_STANDARD 99)
From CMake 3.0.2, it is possible to use add_compile_options (https://cmake.org/cmake/help/latest/command/add_compile_options.html#command:add_compile_options), it is one of the most portable way I found to set C standart.
Take care to use this command before to declare target (with add_library or add_executable).
Below is a CMake script example setting C standart to C99:
add_compile_options(-std=c99)
add_executable(my_exe ${SOURCES})
Add the following to your CMakeLists.txt file and run cmake again
set(CMAKE_C_FLAGS "std=c99")