CMake Tutorial

介绍

CMake教程提供了一个循序渐进的指南,涵盖了CMake帮助解决的常见的系统构建问题。了解示例项目中各种主题是如何一起工作的会非常有帮助。教程文档和示例源代码可以在CMake源代码树的 Help/guide/tutorial  目录中找到。每个步骤都有自己的子目录,其中包含可以用作起点的代码。教程示例是渐进的,因此每个步骤都为前一个步骤提供完整的解决方案。

基础出发点 (Step 1)

最基本的项目是由源代码文件构建的可执行文件。对于简单的项目,只需要三行 CMakeLists.txt文件。这将是我们教程的起点。创建一个  CMakeLists.txt文件在 Step1  目录:

cmake_minimum_required(VERSION 3.10)# set the project name
project(Tutorial)# add the executable
add_executable(Tutorial tutorial.cxx)

注意,这个例子在CMakeLists.txt文件中使用了小写命令。CMake支持大小写混合命令。在 Step1 目录中的教程的源代码 tutorial.cxx 是用来计算一个数字的平方根的。

// tutorial.cxx
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string>int main(int argc, char* argv[])
{if (argc < 2) {std::cout << "Usage: " << argv[0] << " number" << std::endl;return 1;}// convert input to doubleconst double inputValue = atof(argv[1]);// calculate square rootconst double outputValue = sqrt(inputValue);std::cout << "The square root of " << inputValue << " is " << outputValue<< std::endl;return 0;
}

添加版本号和配置头文件

我们要添加的第一个特性是为我们的可执行文件和项目提供一个版本号。虽然我们可以在源代码中来实现这一点,但使用 CMakeLists.txt 提供了更大的灵活性。

首先,修改 CMakeLists.txt 文件使用 project() 命令设置项目名称和版本号。

cmake_minimum_required(VERSION 3.10)# set the project name and version
project(Tutorial VERSION 1.0)

然后,配置一个头文件来将版本号传递给源代码:

configure_file(TutorialConfig.h.in TutorialConfig.h)

由于配置的文件将被写入到二叉树中,所以我们必须将该目录添加到搜索包含文件的路径列表中。在 CMakeLists.txt 文件的末尾添加以下行:

target_include_directories(Tutorial PUBLIC"${PROJECT_BINARY_DIR}")

使用您最喜欢的编辑器,在源目录中创建 TutorialConfig.h.in 包含以下内容:

// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

当 CMake 配置这个头文件时,@Tutorial_VERSION_MAJOR@@Tutorial_VERSION_MINOR@ 的值将被替换。

下一步修改 tutorial.cxx 以包含已配置的头文件 TutorialConfig.h.

最后,让我们通过更新 tutorial.cxx 打印出可执行文件的名称和版本号如下:

  if (argc < 2) {// report versionstd::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."<< Tutorial_VERSION_MINOR << std::endl;std::cout << "Usage: " << argv[0] << " number" << std::endl;return 1;}

指定c++标准

接下来,让我们通过在 tutorial.cxx 中用 std::stod 替换 atof,为我们的项目添加一些c++ 11特性。同时,移除 #include <cstdlib>

  const double inputValue = std::stod(argv[1]);

我们需要在CMake代码中明确声明它应该使用正确的标志。在CMake中启用对特定c++标准的支持的最简单方法是使用 CMAKE_CXX_STANDARD 变量。对于本教程,在 CMakeLists.txt 文件中设置 CMAKE_CXX_STANDARD 变量。将 CMAKE_CXX_STANDARD_REQUIRED 设置为 True 。确保将 CMAKE_CXX_STANDARD 声明添加到 add_executable 调用的上方。

cmake_minimum_required(VERSION 3.10)# set the project name and version
project(Tutorial VERSION 1.0)# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

构建和测试

运行 cmake 可执行文件或 cmake-gui 来配置项目,然后用你选择的构建工具构建它。

例如,我们可以从命令行导航到CMake源代码树的  Help/guide/tutorial 目录,并创建一个构建目录:

mkdir Step1_build

接下来,导航到build目录,运行CMake来配置项目并生成一个本地构建系统:

cd Step1_build
cmake ../Step1

然后调用构建系统来实际编译/链接项目:

cmake --build .

最后,尝试用以下命令来使用新构建的教程:

Tutorial 4294967296
Tutorial 10
Tutorial

添加lib库 (Step 2)

现在我们将向我们的项目添加一个lib库。这个库将包含我们自己的计算数字平方根的实现。可执行文件可以使用这个库,而不是编译器提供的标准平方根函数。

在本教程中,我们将把这个库放入名为 MathFunctions 的子目录中。这个目录已经包含了一个头文件  MathFunctions.h 和一个源文件  mysqrt.cxx,源文件有一个名为 mysqrt 的函数,它提供了与编译器的 sqrt 函数类似的功能。

在 MathFunctions 目录 CMakeLists.txt 文件中添加如下一行:

add_library(MathFunctions mysqrt.cxx)

为了使用这个新库,我们将在根目录的 CMakeLists.txt 中添加一个 add_subdirectory() 调用,使 lib 库将得到构建。我们将新库添加到可执行文件中,并将 MathFunctions 作为 include 目录添加,以便 mqsqrt.h 头文件可以找到。根目录的 CMakeLists.txt 最后几行现在看起来应该像:

# add the MathFunctions library
add_subdirectory(MathFunctions)# add the executable
add_executable(Tutorial tutorial.cxx)target_link_libraries(Tutorial PUBLIC MathFunctions)# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC"${PROJECT_BINARY_DIR}""${PROJECT_SOURCE_DIR}/MathFunctions")

现在让我们使 MathFunctions 库成为可选项。虽然在本教程中没有必要这样做,但对于大型项目来说,这是很常见的情况。第一步是向根目录的 CMakeLists.txt 文件添加一个选项。

option(USE_MYMATH "Use tutorial provided math implementation" ON)# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)

这个选项将在 cmake-gui 和 ccmake 中显示,默认值ON可以由用户更改。该设置将存储在缓存中,这样用户在每次在构建目录上运行CMake时就不需要设置该值。

下一个更改是使构建和链接MathFunctions库成为有条件的。为此,我们更改根目录 CMakeLists.txt 文件如下所示:

if(USE_MYMATH)add_subdirectory(MathFunctions)list(APPEND EXTRA_LIBS MathFunctions)list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()# add the executable
add_executable(Tutorial tutorial.cxx)target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC"${PROJECT_BINARY_DIR}"${EXTRA_INCLUDES})

注意,使用了变量 EXTRA_LIBS 来收集任何可选库,以便稍后链接到可执行文件中。变量 EXTRA_INCLUDES 类似地用于可选头文件。在处理许多可选组件时,这是一种经典的方法,我们将在下一步讨论现代方法。

对源代码的相应更改相当简单。首先在 tutorial.cxx,包括 MathFunctions.h 头文件,如果我们需要:

#ifdef USE_MYMATH
#  include "MathFunctions.h"
#endif

然后,在同一个文件中,让 USE_MYMATH 控件使用哪个平方根函数:

#ifdef USE_MYMATHconst double outputValue = mysqrt(inputValue);
#elseconst double outputValue = sqrt(inputValue);
#endif

由于源代码现在需要 USE_MYMATH ,我们可以把它添加到 TutorialConfig.h.in 如下:

#cmakedefine USE_MYMATH

练习:为什么在 USE_MYMATH 配置 TutorialConfig.h.in 很重要?如果我们把这两个颠倒过来会发生什么?

运行 cmake 可执行文件或 cmake-gui 来配置项目,然后用你选择的构建工具构建它。然后运行构建的教程可执行文件。

现在让我们更新 USE_MYMATH 的值。最简单的方法是使用 cmake-gui 或 ccmake (如果你在终端上的话)。或者,如果你想从命令行更改选项,试试:

cmake ../Step2 -DUSE_MYMATH=OFF

重新生成并再次运行教程。

哪个函数给出了更好的结果,sqrt 还是 mysqrt?

添加库的使用需求(Step 3)

使用需求允许对库或可执行文件的链接和include行进行更好的控制,同时也允许对 CMake 内部目标的传递属性进行更多的控制。利用使用需求的主要命令是:

  • target_compile_definitions()

  • target_compile_options()

  • target_include_directories()

  • target_link_libraries()

让我们从添加库(Step 2)开始重构代码,以使用现代的CMake方法满足使用需求。我们首先声明,任何链接到 MathFunctions 的人都需要包括当前的源目录,而MathFunctions本身不需要。因此,这可以成为一个 INTERFACE 使用要求。

请记住,INTERFACE 指的是消费者需要但生产者不需要的东西。在  MathFunctions/CMakeLists.txt 末尾添加以下几行:

target_include_directories(MathFunctionsINTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

现在我们已经指定了MathFunctions的使用要求,可以安全地从根目录 CMakeLists.txt 中删除 EXTRA_INCLUDES 变量的使用。

if(USE_MYMATH)add_subdirectory(MathFunctions)list(APPEND EXTRA_LIBS MathFunctions)
endif()

在这里:

target_include_directories(Tutorial PUBLIC"${PROJECT_BINARY_DIR}")

一旦完成,运行 cmake 可执行文件或 cmake-gui 来配置项目,然后从构建目录用你选择的构建工具或使用 cmake --build .

Installing and Testing (Step 4)

Now we can start adding install rules and testing support to our project.

Install Rules

The install rules are fairly simple: for MathFunctions we want to install the library and header file and for the application we want to install the executable and configured header.

So to the end of MathFunctions/CMakeLists.txt we add:

install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)

And to the end of the top-level CMakeLists.txt we add:

install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"DESTINATION include)

That is all that is needed to create a basic local install of the tutorial.

Now run the cmake executable or the cmake-gui to configure the project and then build it with your chosen build tool.

Then run the install step by using the install option of the cmake command (introduced in 3.15, older versions of CMake must use make install) from the command line. For multi-configuration tools, don’t forget to use the --config argument to specify the configuration. If using an IDE, simply build the INSTALL target. This step will install the appropriate header files, libraries, and executables. For example:

cmake --install .

The CMake variable CMAKE_INSTALL_PREFIX is used to determine the root of where the files will be installed. If using the cmake --install command, the installation prefix can be overridden via the --prefix argument. For example:

cmake --install . --prefix "/home/myuser/installdir"

Navigate to the install directory and verify that the installed Tutorial runs.

Testing Support

Next let’s test our application. At the end of the top-level CMakeLists.txt file we can enable testing and then add a number of basic tests to verify that the application is working correctly.

enable_testing()# does the application run
add_test(NAME Runs COMMAND Tutorial 25)# does the usage message work?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(UsagePROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")# define a function to simplify adding tests
function(do_test target arg result)add_test(NAME Comp${arg} COMMAND ${target} ${arg})set_tests_properties(Comp${arg}PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endfunction(do_test)# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is [-nan|nan|0]")
do_test(Tutorial 0.0001 "0.0001 is 0.01")

The first test simply verifies that the application runs, does not segfault or otherwise crash, and has a zero return value. This is the basic form of a CTest test.

The next test makes use of the PASS_REGULAR_EXPRESSION test property to verify that the output of the test contains certain strings. In this case, verifying that the usage message is printed when an incorrect number of arguments are provided.

Lastly, we have a function called do_test that runs the application and verifies that the computed square root is correct for given input. For each invocation of do_test, another test is added to the project with a name, input, and expected results based on the passed arguments.

Rebuild the application and then cd to the binary directory and run the ctest executable: ctest -N and ctest -VV. For multi-config generators (e.g. Visual Studio), the configuration type must be specified. To run tests in Debug mode, for example, use ctest -C Debug -VV from the build directory (not the Debug subdirectory!). Alternatively, build the RUN_TESTS target from the IDE.

Adding System Introspection (Step 5)

Let us consider adding some code to our project that depends on features the target platform may not have. For this example, we will add some code that depends on whether or not the target platform has the log and exp functions. Of course almost every platform has these functions but for this tutorial assume that they are not common.

If the platform has log and exp then we will use them to compute the square root in the mysqrt function. We first test for the availability of these functions using the CheckSymbolExists module in the top-level CMakeLists.txt. On some platforms, we will need to link to the m library. If log and exp are not initially found, require the m library and try again.

We’re going to use the new defines in TutorialConfig.h.in, so be sure to set them before that file is configured.

include(CheckSymbolExists)
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(NOT (HAVE_LOG AND HAVE_EXP))unset(HAVE_LOG CACHE)unset(HAVE_EXP CACHE)set(CMAKE_REQUIRED_LIBRARIES "m")check_symbol_exists(log "math.h" HAVE_LOG)check_symbol_exists(exp "math.h" HAVE_EXP)if(HAVE_LOG AND HAVE_EXP)target_link_libraries(MathFunctions PRIVATE m)endif()
endif()

Now let’s add these defines to TutorialConfig.h.in so that we can use them from mysqrt.cxx:

// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP

If log and exp are available on the system, then we will use them to compute the square root in the mysqrt function. Add the following code to the mysqrt function in MathFunctions/mysqrt.cxx (don’t forget the #endif before returning the result!):

#if defined(HAVE_LOG) && defined(HAVE_EXP)double result = exp(log(x) * 0.5);std::cout << "Computing sqrt of " << x << " to be " << result<< " using log and exp" << std::endl;
#elsedouble result = x;

We will also need to modify mysqrt.cxx to include cmath.

#include <cmath>

Run the cmake executable or the cmake-gui to configure the project and then build it with your chosen build tool and run the Tutorial executable.

You will notice that we’re not using log and exp, even if we think they should be available. We should realize quickly that we have forgotten to include TutorialConfig.h in mysqrt.cxx.

We will also need to update MathFunctions/CMakeLists.txt so mysqrt.cxx knows where this file is located:

target_include_directories(MathFunctionsINTERFACE ${CMAKE_CURRENT_SOURCE_DIR}PRIVATE ${CMAKE_BINARY_DIR})

After making this update, go ahead and build the project again and run the built Tutorial executable. If log and exp are still not being used, open the generated TutorialConfig.h file from the build directory. Maybe they aren’t available on the current system?

Which function gives better results now, sqrt or mysqrt?

Specify Compile Definition

Is there a better place for us to save the HAVE_LOG and HAVE_EXP values other than in TutorialConfig.h? Let’s try to use target_compile_definitions().

First, remove the defines from TutorialConfig.h.in. We no longer need to include TutorialConfig.h from mysqrt.cxx or the extra include in MathFunctions/CMakeLists.txt.

Next, we can move the check for HAVE_LOG and HAVE_EXP to MathFunctions/CMakeLists.txt and then specify those values as PRIVATE compile definitions.

include(CheckSymbolExists)
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(NOT (HAVE_LOG AND HAVE_EXP))unset(HAVE_LOG CACHE)unset(HAVE_EXP CACHE)set(CMAKE_REQUIRED_LIBRARIES "m")check_symbol_exists(log "math.h" HAVE_LOG)check_symbol_exists(exp "math.h" HAVE_EXP)if(HAVE_LOG AND HAVE_EXP)target_link_libraries(MathFunctions PRIVATE m)endif()
endif()# add compile definitions
if(HAVE_LOG AND HAVE_EXP)target_compile_definitions(MathFunctionsPRIVATE "HAVE_LOG" "HAVE_EXP")
endif()

After making these updates, go ahead and build the project again. Run the built Tutorial executable and verify that the results are same as earlier in this step.

Adding a Custom Command and Generated File (Step 6)

Suppose, for the purpose of this tutorial, we decide that we never want to use the platform log and exp functions and instead would like to generate a table of precomputed values to use in the mysqrt function. In this section, we will create the table as part of the build process, and then compile that table into our application.

First, let’s remove the check for the log and exp functions in MathFunctions/CMakeLists.txt. Then remove the check for HAVE_LOG and HAVE_EXP from mysqrt.cxx. At the same time, we can remove #include <cmath>.

In the MathFunctions subdirectory, a new source file named MakeTable.cxx has been provided to generate the table.

After reviewing the file, we can see that the table is produced as valid C++ code and that the output filename is passed in as an argument.

The next step is to add the appropriate commands to the MathFunctions/CMakeLists.txt file to build the MakeTable executable and then run it as part of the build process. A few commands are needed to accomplish this.

First, at the top of MathFunctions/CMakeLists.txt, the executable for MakeTable is added as any other executable would be added.

add_executable(MakeTable MakeTable.cxx)

Then we add a custom command that specifies how to produce Table.h by running MakeTable.

add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.hCOMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.hDEPENDS MakeTable)

Next we have to let CMake know that mysqrt.cxx depends on the generated file Table.h. This is done by adding the generated Table.h to the list of sources for the library MathFunctions.

add_library(MathFunctionsmysqrt.cxx${CMAKE_CURRENT_BINARY_DIR}/Table.h)

We also have to add the current binary directory to the list of include directories so that Table.h can be found and included by mysqrt.cxx.

target_include_directories(MathFunctionsINTERFACE ${CMAKE_CURRENT_SOURCE_DIR}PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

Now let’s use the generated table. First, modify mysqrt.cxx to include Table.h. Next, we can rewrite the mysqrt function to use the table:

double mysqrt(double x)
{if (x <= 0) {return 0;}// use the table to help find an initial valuedouble result = x;if (x >= 1 && x < 10) {std::cout << "Use the table to help find an initial value " << std::endl;result = sqrtTable[static_cast<int>(x)];}// do ten iterationsfor (int i = 0; i < 10; ++i) {if (result <= 0) {result = 0.1;}double delta = x - (result * result);result = result + 0.5 * delta / result;std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;}return result;
}

Run the cmake executable or the cmake-gui to configure the project and then build it with your chosen build tool.

When this project is built it will first build the MakeTable executable. It will then run MakeTable to produce Table.h. Finally, it will compile mysqrt.cxx which includes Table.h to produce the MathFunctions library.

Run the Tutorial executable and verify that it is using the table.

Building an Installer (Step 7)

Next suppose that we want to distribute our project to other people so that they can use it. We want to provide both binary and source distributions on a variety of platforms. This is a little different from the install we did previously in Installing and Testing (Step 4) , where we were installing the binaries that we had built from the source code. In this example we will be building installation packages that support binary installations and package management features. To accomplish this we will use CPack to create platform specific installers. Specifically we need to add a few lines to the bottom of our top-level CMakeLists.txt file.

include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
include(CPack)

That is all there is to it. We start by including InstallRequiredSystemLibraries. This module will include any runtime libraries that are needed by the project for the current platform. Next we set some CPack variables to where we have stored the license and version information for this project. The version information was set earlier in this tutorial and the license.txt has been included in the top-level source directory for this step.

Finally we include the CPack module which will use these variables and some other properties of the current system to setup an installer.

The next step is to build the project in the usual manner and then run the cpack executable. To build a binary distribution, from the binary directory run:

cpack

To specify the generator, use the -G option. For multi-config builds, use -C to specify the configuration. For example:

cpack -G ZIP -C Debug

To create a source distribution you would type:

cpack --config CPackSourceConfig.cmake

Alternatively, run make package or right click the Package target and Build Project from an IDE.

Run the installer found in the binary directory. Then run the installed executable and verify that it works.

Adding Support for a Dashboard (Step 8)

Adding support for submitting our test results to a dashboard is simple. We already defined a number of tests for our project in Testing Support. Now we just have to run those tests and submit them to a dashboard. To include support for dashboards we include the CTest module in our top-level CMakeLists.txt.

Replace:

# enable testing
enable_testing()

With:

# enable dashboard scripting
include(CTest)

The CTest module will automatically call enable_testing(), so we can remove it from our CMake files.

We will also need to create a CTestConfig.cmake file in the top-level directory where we can specify the name of the project and where to submit the dashboard.

set(CTEST_PROJECT_NAME "CMakeTutorial")
set(CTEST_NIGHTLY_START_TIME "00:00:00 EST")set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION "/submit.php?project=CMakeTutorial")
set(CTEST_DROP_SITE_CDASH TRUE)

The ctest executable will read in this file when it runs. To create a simple dashboard you can run the cmake executable or the cmake-gui to configure the project, but do not build it yet. Instead, change directory to the binary tree, and then run:

ctest [-VV] -D Experimental

Remember, for multi-config generators (e.g. Visual Studio), the configuration type must be specified:

ctest [-VV] -C Debug -D Experimental

Or, from an IDE, build the Experimental target.

The ctest executable will build and test the project and submit the results to Kitware’s public dashboard: https://my.cdash.org/index.php?project=CMakeTutorial.

Mixing Static and Shared (Step 9)

In this section we will show how the BUILD_SHARED_LIBS variable can be used to control the default behavior of add_library(), and allow control over how libraries without an explicit type (STATICSHAREDMODULE or OBJECT) are built.

To accomplish this we need to add BUILD_SHARED_LIBS to the top-level CMakeLists.txt. We use the option() command as it allows users to optionally select if the value should be ON or OFF.

Next we are going to refactor MathFunctions to become a real library that encapsulates using mysqrt or sqrt, instead of requiring the calling code to do this logic. This will also mean that USE_MYMATH will not control building MathFunctions, but instead will control the behavior of this library.

The first step is to update the starting section of the top-level CMakeLists.txt to look like:

cmake_minimum_required(VERSION 3.10)# set the project name and version
project(Tutorial VERSION 1.0)# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)# control where the static and shared libraries are built so that on windows
# we don't need to tinker with the path to run the executable
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")option(BUILD_SHARED_LIBS "Build using shared libraries" ON)# configure a header file to pass the version number only
configure_file(TutorialConfig.h.in TutorialConfig.h)# add the MathFunctions library
add_subdirectory(MathFunctions)# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)

Now that we have made MathFunctions always be used, we will need to update the logic of that library. So, in MathFunctions/CMakeLists.txt we need to create a SqrtLibrary that will conditionally be built and installed when USE_MYMATH is enabled. Now, since this is a tutorial, we are going to explicitly require that SqrtLibrary is built statically.

The end result is that MathFunctions/CMakeLists.txt should look like:

# add the library that runs
add_library(MathFunctions MathFunctions.cxx)# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
target_include_directories(MathFunctionsINTERFACE ${CMAKE_CURRENT_SOURCE_DIR})# should we use our own math functions
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")# first we add the executable that generates the tableadd_executable(MakeTable MakeTable.cxx)# add the command to generate the source codeadd_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.hCOMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.hDEPENDS MakeTable)# library that just does sqrtadd_library(SqrtLibrary STATICmysqrt.cxx${CMAKE_CURRENT_BINARY_DIR}/Table.h)# state that we depend on our binary dir to find Table.htarget_include_directories(SqrtLibrary PRIVATE${CMAKE_CURRENT_BINARY_DIR})target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()# define the symbol stating we are using the declspec(dllexport) when
# building on windows
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")# install rules
set(installable_libs MathFunctions)
if(TARGET SqrtLibrary)list(APPEND installable_libs SqrtLibrary)
endif()
install(TARGETS ${installable_libs} DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)

Next, update MathFunctions/mysqrt.cxx to use the mathfunctions and detail namespaces:

#include <iostream>#include "MathFunctions.h"// include the generated table
#include "Table.h"namespace mathfunctions {
namespace detail {
// a hack square root calculation using simple operations
double mysqrt(double x)
{if (x <= 0) {return 0;}// use the table to help find an initial valuedouble result = x;if (x >= 1 && x < 10) {std::cout << "Use the table to help find an initial value " << std::endl;result = sqrtTable[static_cast<int>(x)];}// do ten iterationsfor (int i = 0; i < 10; ++i) {if (result <= 0) {result = 0.1;}double delta = x - (result * result);result = result + 0.5 * delta / result;std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;}return result;
}
}
}

We also need to make some changes in tutorial.cxx, so that it no longer uses USE_MYMATH:

  1. Always include MathFunctions.h

  2. Always use mathfunctions::sqrt

  3. Don’t include cmath

Finally, update MathFunctions/MathFunctions.h to use dll export defines:

#if defined(_WIN32)
#  if defined(EXPORTING_MYMATH)
#    define DECLSPEC __declspec(dllexport)
#  else
#    define DECLSPEC __declspec(dllimport)
#  endif
#else // non windows
#  define DECLSPEC
#endifnamespace mathfunctions {
double DECLSPEC sqrt(double x);
}

At this point, if you build everything, you may notice that linking fails as we are combining a static library without position independent code with a library that has position independent code. The solution to this is to explicitly set the POSITION_INDEPENDENT_CODE target property of SqrtLibrary to be True no matter the build type.

  # state that SqrtLibrary need PIC when the default is shared librariesset_target_properties(SqrtLibrary PROPERTIESPOSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS})target_link_libraries(MathFunctions PRIVATE SqrtLibrary)

Exercise: We modified MathFunctions.h to use dll export defines. Using CMake documentation can you find a helper module to simplify this?

Adding Generator Expressions (Step 10)

Generator expressions are evaluated during build system generation to produce information specific to each build configuration.

Generator expressions are allowed in the context of many target properties, such as LINK_LIBRARIESINCLUDE_DIRECTORIESCOMPILE_DEFINITIONS and others. They may also be used when using commands to populate those properties, such as target_link_libraries()target_include_directories()target_compile_definitions() and others.

Generator expressions may be used to enable conditional linking, conditional definitions used when compiling, conditional include directories and more. The conditions may be based on the build configuration, target properties, platform information or any other queryable information.

There are different types of generator expressions including Logical, Informational, and Output expressions.

Logical expressions are used to create conditional output. The basic expressions are the 0 and 1 expressions. A $<0:...> results in the empty string, and <1:...> results in the content of “…”. They can also be nested.

A common usage of generator expressions is to conditionally add compiler flags, such as those for language levels or warnings. A nice pattern is to associate this information to an INTERFACE target allowing this information to propagate. Let’s start by constructing an INTERFACE target and specifying the required C++ standard level of 11 instead of using CMAKE_CXX_STANDARD.

So the following code:

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

Would be replaced with:

add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)

Next we add the desired compiler warning flags that we want for our project. As warning flags vary based on the compiler we use the COMPILE_LANG_AND_ID generator expression to control which flags to apply given a language and a set of compiler ids as seen below:

set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(tutorial_compiler_flags INTERFACE"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>""$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)

Looking at this we see that the warning flags are encapsulated inside a BUILD_INTERFACE condition. This is done so that consumers of our installed project will not inherit our warning flags.

Exercise: Modify MathFunctions/CMakeLists.txt so that all targets have a target_link_libraries() call to tutorial_compiler_flags.

Adding Export Configuration (Step 11)

During Installing and Testing (Step 4) of the tutorial we added the ability for CMake to install the library and headers of the project. During Building an Installer (Step 7) we added the ability to package up this information so it could be distributed to other people.

The next step is to add the necessary information so that other CMake projects can use our project, be it from a build directory, a local install or when packaged.

The first step is to update our install(TARGETS) commands to not only specify a DESTINATION but also an EXPORT. The EXPORT keyword generates and installs a CMake file containing code to import all targets listed in the install command from the installation tree. So let’s go ahead and explicitly EXPORT the MathFunctions library by updating the install command in MathFunctions/CMakeLists.txt to look like:

set(installable_libs MathFunctions tutorial_compiler_flags)
if(TARGET SqrtLibrary)list(APPEND installable_libs SqrtLibrary)
endif()
install(TARGETS ${installable_libs}DESTINATION libEXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)

Now that we have MathFunctions being exported, we also need to explicitly install the generated MathFunctionsTargets.cmake file. This is done by adding the following to the bottom of the top-level CMakeLists.txt:

install(EXPORT MathFunctionsTargetsFILE MathFunctionsTargets.cmakeDESTINATION lib/cmake/MathFunctions
)

At this point you should try and run CMake. If everything is setup properly you will see that CMake will generate an error that looks like:

Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:"/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"which is prefixed in the source directory.

What CMake is trying to say is that during generating the export information it will export a path that is intrinsically tied to the current machine and will not be valid on other machines. The solution to this is to update the MathFunctions target_include_directories() to understand that it needs different INTERFACE locations when being used from within the build directory and from an install / package. This means converting the target_include_directories() call for MathFunctions to look like:

target_include_directories(MathFunctionsINTERFACE$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>$<INSTALL_INTERFACE:include>)

Once this has been updated, we can re-run CMake and verify that it doesn’t warn anymore.

At this point, we have CMake properly packaging the target information that is required but we will still need to generate a MathFunctionsConfig.cmake so that the CMake find_package() command can find our project. So let’s go ahead and add a new file to the top-level of the project called Config.cmake.in with the following contents:

@PACKAGE_INIT@include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )

Then, to properly configure and install that file, add the following to the bottom of the top-level CMakeLists.txt:

install(EXPORT MathFunctionsTargetsFILE MathFunctionsTargets.cmakeDESTINATION lib/cmake/MathFunctions
)include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"INSTALL_DESTINATION "lib/cmake/example"NO_SET_AND_CHECK_MACRONO_CHECK_REQUIRED_COMPONENTS_MACRO)
# generate the version file for the config file
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"COMPATIBILITY AnyNewerVersion
)# install the configuration file
install(FILES${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmakeDESTINATION lib/cmake/MathFunctions)

At this point, we have generated a relocatable CMake Configuration for our project that can be used after the project has been installed or packaged. If we want our project to also be used from a build directory we only have to add the following to the bottom of the top level CMakeLists.txt:

export(EXPORT MathFunctionsTargetsFILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)

With this export call we now generate a Targets.cmake, allowing the configured MathFunctionsConfig.cmake in the build directory to be used by other projects, without needing it to be installed.

Packaging Debug and Release (Step 12)

Note: This example is valid for single-configuration generators and will not work for multi-configuration generators (e.g. Visual Studio).

By default, CMake’s model is that a build directory only contains a single configuration, be it Debug, Release, MinSizeRel, or RelWithDebInfo. It is possible, however, to setup CPack to bundle multiple build directories and construct a package that contains multiple configurations of the same project.

First, we want to ensure that the debug and release builds use different names for the executables and libraries that will be installed. Let’s use d as the postfix for the debug executable and libraries.

Set CMAKE_DEBUG_POSTFIX near the beginning of the top-level CMakeLists.txt file:

set(CMAKE_DEBUG_POSTFIX d)add_library(tutorial_compiler_flags INTERFACE)

And the DEBUG_POSTFIX property on the tutorial executable:

add_executable(Tutorial tutorial.cxx)
set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})target_link_libraries(Tutorial PUBLIC MathFunctions)

Let’s also add version numbering to the MathFunctions library. In MathFunctions/CMakeLists.txt, set the VERSION and SOVERSION properties:

set_property(TARGET MathFunctions PROPERTY VERSION "1.0.0")
set_property(TARGET MathFunctions PROPERTY SOVERSION "1")

From the Step12 directory, create debug and release subbdirectories. The layout will look like:

- Step12- debug- release

Now we need to setup debug and release builds. We can use CMAKE_BUILD_TYPE to set the configuration type:

cd debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build .
cd ../release
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .

Now that both the debug and release builds are complete, we can use a custom configuration file to package both builds into a single release. In the Step12 directory, create a file called MultiCPackConfig.cmake. In this file, first include the default configuration file that was created by the cmake executable.

Next, use the CPACK_INSTALL_CMAKE_PROJECTS variable to specify which projects to install. In this case, we want to install both debug and release.

include("release/CPackConfig.cmake")set(CPACK_INSTALL_CMAKE_PROJECTS"debug;Tutorial;ALL;/""release;Tutorial;ALL;/")

From the Step12 directory, run cpack specifying our custom configuration file with the config option:

cpack --config MultiCPackConfig.cmake

CMake Tutorial相关推荐

  1. CMake Tutorial Step1

    CMake Tutorial Step1 参考资料:Step 1: A Basic Starting Point - CMake 3.26.3 Documentation Tutorial工程:官方T ...

  2. CMake入门指南-编译教程

    CMake是一个比make更高级的编译配置工具,它可以根据不同平台.不同的编译器,生成相应的Makefile或者vcproj项目. 通过编写CMakeLists.txt,可以控制生成的Makefile ...

  3. cmake使用教程(一)-起步

    [cmake系列使用教程] cmake使用教程(一)-起步 cmake使用教程(二)-添加库 cmake使用教程(三)-安装.测试.系统自检 cmake使用教程(四)-文件生成器 cmake使用教程( ...

  4. CMake 用法导览

    Preface : 本文是CMake官方文档CMake Tutorial (http://www.cmake.org/cmake/help/cmake_tutorial.html) 的翻译.通过一个样 ...

  5. vs2017 cmake android,CMake构建VS2017工程

    1 安装VS2017/cmake 2 工程开发 3 生成VS2017工程 4 参考资料 1 安装VS2017/cmake 软件安装: Visual Studio官网下载Visual Studio Co ...

  6. CMake快速入门01:CMake简介与安装

    目录 1 CMake简介 2 CMake安装 3 CMake基本使用 3.1 实验源文件 3.2 实验CMakeLists.txt 3.2.1 CMakeList.txt命令概述 3.2.2 add_ ...

  7. 用CMake编译lua

    最近的工作是基于premake做一个适合我们公司的C++的编译系统,了解一下已经比较成熟的CMake,是非常有参考价值的. [CMake初印象] CMake和premake一样,都是meta buil ...

  8. 使用CMake构建/开始使用CMake

    Getting started with CMake 开始使用CMake CMake is a group of tools that allow to build, test, and packag ...

  9. Cmake Ninja

    CMake install Get the Software,下载对应平台上的压缩包即可. eg. linux 平台上用 wget https://github.com/Kitware/CMake/r ...

最新文章

  1. poj2112(floyd+二分+二分图多重匹配)
  2. 关于ES性能调优几件必须知道的事
  3. 信捷步进指令的使用_【笔记】信捷plc应用,指令篇
  4. 金山手机控usb调试模式开启工具_不看不知道手机有多卡!一款深挖手机的良心工具...
  5. python的三元运算
  6. 八大算法思想(二)------------------递归算法
  7. 我这么玩Web Api(一):帮助页面或用户手册(Microsoft and Swashbuckle Help Page)
  8. docker镜像的使用及相关
  9. html添加变量参数吗,动态CSS与变量参数? (可能吗?)
  10. 360 RePlugin 初探
  11. Python制作音乐播放器
  12. C# 谷歌邮箱发送邮件
  13. 高歌——【先声夺银】四种能力判断你的炒白银水平
  14. SpringBoot打成jar包部署,Excel模板下载文件损坏,提示恢复问题处理
  15. 【倾心整理】高级工程师手写总结,入门到顶级程序员的学习方法
  16. 【BZOJ1226/SDOI2009】学校食堂Dining
  17. iframe嵌入网页的用法
  18. 2030年的6G:5大趋势,13个核心技术
  19. VMware虚拟机桥接模式配置,设置虚拟机连接公网
  20. 锐捷(一)清除三层交换机配置

热门文章

  1. U盘html文件恢复不了,u盘文件突然不见了怎么恢复?恢复小技巧来了
  2. 股票入门浅学20210721
  3. vue中使用layui实现树形菜单增删改查功能
  4. MySQL 支持表情字符
  5. 自己写一个strcmp函数
  6. Asp 操作Access数据库时出现死锁.ldb的解决方法
  7. 好书推荐之《麦田里的守望者》 隐私策略(Privacy policy)
  8. python全局变量(模块法和global)
  9. SQL Server 2012新建本地服务器组注册服务器
  10. TCP: too many of orphaned sockets报错解决