CMake Tutorial 巡礼(2)_ 添加库
这是本系列的第三篇。
上一篇,我们学习了CMake的一些基本操作,包括如何搭建编译系统、如何添加版本号及头文件、如何指定C++标准等等。
我们在日常构建项目的过程中,用于项目组织的代码可能不止一个,这就引入了库的概念,本篇就来学习一下如何在CMake中添加库。
本章导读
第二步:添加库
Now we will add a library to our project. This library will contain our own implementation for computing the square root of a number. The executable can then use this library instead of the standard square root function provided by the compiler.
现在我们将会在项目中添加一个库。这个库将包含我们自己对计算一个数的平方根的实现。可执行文件将可以调用这个库,而不是由编译器提供的标准平方根函数。
For this tutorial we will put the library into a subdirectory called
MathFunctions
. This directory already contains a header file,MathFunctions.h
, and a source filemysqrt.cxx
. The source file has one function calledmysqrt
that provides similar functionality to the compiler’ssqrt
function.
在tutorial中我们将会把这个库放到一个名为MathFunctions
的子路径下。这个路径已经包含有一个头文件,即MathFunctions.h
,以及一个源文件mysqrt.cxx
。这个源文件包含一个名为mysqrt
的函数,提供了和编译器的sqrt
函数类似的功能。
小白按:原始的MathFunctions.h
和mysqrt.cxx
文件内容如下所示:
MathFunctions.h
double mysqrt(double x);
mysqrt.cxx
#include <iostream>
// a hack square root calculation using simple operations 使用简单操作实现的开方运算函数
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
double result = x;
// do ten iterations 做十次迭代
for (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;
}
Add the following one line
CMakeLists.txt
file to theMathFunctions
directory:
添加以下行到MathFunctions
路径下的CMakeLists.txt
文件中:
MathFunctions/CMakeLists.txt
add_library(MathFunctions mysqrt.cxx)
To make use of the new library we will add an
add_subdirectory()
call in the top-levelCMakeLists.txt
file so that the library will get built. We add the new library to the executable, and addMathFunctions
as an include directory so that theMathFunctions.h
header file can be found. The last few lines of the top-levelCMakeLists.txt
file should now look like:
为了使用新的库,我们将会在顶层的CMakeLists.txt
文件中添加一个add_subdirectory()
调用, 这样库就可以被编译。我们将新库添加到可执行中去,并且添加MathFunctions
为一个包含目录,于是MathFunctions.h
头文件就可以被找到。顶层路径的CMakeLists.txt
文件的最后几行应该被改成如下形式:
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 这样我们才能找得到TutorialConfig.h头文件
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
Now let us make the
MathFunctions
library optional. While for the tutorial there really isn’t any need to do so, for larger projects this is a common occurrence. The first step is to add an option to the top-levelCMakeLists.txt
file.
现在让我们把MathFunctions
目录变成可选。尽管对于tutorial来说没有任何必要这样做,但对于大型项目来说这是一个常规操作。第一步就是在顶层的CMakeLists.txt
文件中添加一个选项。
CMakeLists.txt
option(USE_MYMATH "Use tutorial provided math implementation" ON)
# configure a header file to pass some of the CMake settings 指定一个头文件来向源代码中传递一些CMake设置
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)
This option will be displayed in the
cmake-gui
andccmake
with a default value ofON
that can be changed by the user. This setting will be stored in the cache so that the user does not need to set the value each time they run CMake on a build directory.
这个选项将会显示在 cmake-gui
以及 ccmake
中,以一个默认值为ON
、可以由用户进行修改的形式出现。这个设置将会保存在缓存中,这样当用户在编译目录下运行CMake时,不需要每次都对它进行设置。
The next change is to make building and linking the
MathFunctions
library conditional. To do this, we will create anif
statement which checks the value of the option. Inside theif
block, put theadd_subdirectory()
command from above with some additional list commands to store information needed to link to the library and add the subdirectory as an include directory in theTutorial
target. The end of the top-levelCMakeLists.txt
file will now look like the following:
另一处修改是让编译和链接到MathFunctions
库的操作变成有条件的。为了实现这个目标,我们将会创建一个检查这个选项的if
声明。在if
块之内,需要将上面提到的add_subdirectory()
命令移动到此,并且追加一些添加信息的命令,这些命令需要链接到库,并且在Tutorial
目标中添加一个子目录到包含目录中去。顶层的CMakeLists.txt
文件末尾将会看起来如下所示:
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 这样我们才能找得到TutorialConfig.h头文件。
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}
)
Note the use of the variable
EXTRA_LIBS
to collect up any optional libraries to later be linked into the executable. The variableEXTRA_INCLUDES
is used similarly for optional header files. This is a classic approach when dealing with many optional components, we will cover the modern approach in the next step.
请注意,使用变量EXTRA_LIBS
收集任何的可选库,以便稍后链接到可执行文件中。变量EXTRA_INCLUDES
类似地用于可选头文件。这是处理许多可选组件时的经典方法,我们将在下一步中介绍现代方法。
The corresponding changes to the source code are fairly straightforward. First, in
tutorial.cxx
, include theMathFunctions.h
header if we need it:
对源代码的相应修改相当直接。首先,在tutorial.cxx
中,如果需要的话,请包含MathFunction.h
头文件:
tutorial.cxx
#ifdef USE_MYMATH
# include "MathFunctions.h"
#endif
接着,在同一个文件里,设置USE_MYMATH
控制什么情况下会使用什么版本的平方根函数:
tutorial.cxx
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif
Since the source code now requires
USE_MYMATH
we can add it toTutorialConfig.h.in
with the following line:
由于源代码现在需要USE_MYMATH
,我们可以使用以下行将其添加到TutorialConfig.h.in
文件中去:
TutorialConfig.h.in
#cmakedefine USE_MYMATH
小白按:最终改造后,各个文件的内容分别是:
CMakeLists.txt
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)
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)
if (USE_MYMATH)
#add the MathFunctions library
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}
)
tutorial.cxx
// A simple program that computes the square root of a number
#include <cmath>
#include <iostream>
#include <string>
#include "TutorialConfig.h"
#ifdef USE_MYMATH
# include "MathFunctions.h"
#endif
int main(int argc, char* argv[])
{
if (argc < 2) {
// report version
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}
// convert input to double
const double inputValue = std::stod(argv[1]);
// calculate square root
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif
std::cout << "The square root of " << inputValue << " is " << outputValue
<< std::endl;
return 0;
}
TutorialConfig.h.in
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
#cmakedefine USE_MYMATH
Exercise: Why is it important that we configure
TutorialConfig.h.in
after the option forUSE_MYMATH
? What would happen if we inverted the two?
【练习】为什么在USE_MYMATH
选项之后再配置TutorialConfig.h.in
是很重要的?如果把这二者的顺序对调,会发生什么?
小白按:因为TutorialConfg.h.in
中使用了USE_MYMATH
变量,如果对调二者顺序,则会出现未定义错误。
Run the
cmake
executable or thecmake-gui
to configure the project and then build it with your chosen build tool. Then run the built Tutorial executable.
运行 cmake
可执行文件或 cmake-gui
来指定项目,并且用你选定的编译工具来进行编译。然后运行Tutorial的可执行文件。
Now let’s update the value of
USE_MYMATH
. The easiest way is to use thecmake-gui
orccmake
if you’re in the terminal. Or, alternatively, if you want to change the option from the command-line, try:
现在让我们来更新USE_MYMATH
的值,最简单的办法是使用 cmake-gui
,或者如果你使用终端环境的话可以直接使用cmake
。也或者,如果你想要在命令行中修改这个选项,尝试:
cmake ../Step2 -DUSE_MYMATH=OFF
Rebuild and run the tutorial again.
重新编译并运行tutorial.exe。
Which function gives better results,
sqrt
ormysqrt
?
哪种函数给出了更好的结果,sqrt
还是mysqrt
?
小白按:与上一篇中采用相同的测试数,得到的结果为:
Computing sqrt of 4.29497e+09 to be 2.14748e+09
Computing sqrt of 4.29497e+09 to be 1.07374e+09
Computing sqrt of 4.29497e+09 to be 5.36871e+08
Computing sqrt of 4.29497e+09 to be 2.68435e+08
Computing sqrt of 4.29497e+09 to be 1.34218e+08
Computing sqrt of 4.29497e+09 to be 6.71089e+07
Computing sqrt of 4.29497e+09 to be 3.35545e+07
Computing sqrt of 4.29497e+09 to be 1.67773e+07
Computing sqrt of 4.29497e+09 to be 8.38878e+06
Computing sqrt of 4.29497e+09 to be 4.19465e+06
The square root of 4.29497e+09 is 4.19465e+06
Computing sqrt of 10 to be 5.5
Computing sqrt of 10 to be 3.65909
Computing sqrt of 10 to be 3.19601
Computing sqrt of 10 to be 3.16246
Computing sqrt of 10 to be 3.16228
Computing sqrt of 10 to be 3.16228
Computing sqrt of 10 to be 3.16228
Computing sqrt of 10 to be 3.16228
Computing sqrt of 10 to be 3.16228
Computing sqrt of 10 to be 3.16228
The square root of 10 is 3.16228
Tutorial Version 1.0
Usage: Tutorial number
显然,sqrt
得到了更高精度的结果,对于大数的开方运算,甚至得到了错误的答案,这是因为mysqrt
中使用的方法只迭代了10次,远远达不到需要的精度。
【水平所限,错漏难免,创作不易,轻喷勿骂】