CMake 秘籍(七)(2)https://developer.aliyun.com/article/1525424
如何操作
我们将按照以下步骤在这个交叉编译的“Hello World”示例中创建三个文件:
- 创建一个目录,其中包含
hello-world.cpp
和前面列出的CMakeLists.txt
。 - 创建一个
toolchain.cmake
文件,其中包含以下内容:
# the name of the target operating system set(CMAKE_SYSTEM_NAME Windows) # which compilers to use set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) # adjust the default behaviour of the find commands: # search headers and libraries in the target environment set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # search programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
- 将
CMAKE_CXX_COMPILER
调整为相应的编译器(路径)。 - 然后,通过指向
CMAKE_TOOLCHAIN_FILE
到工具链文件来配置代码(在本例中,使用了从源代码构建的 MXE 编译器):
$ mkdir -p build $ cd build $ cmake -D CMAKE_TOOLCHAIN_FILE=toolchain.cmake .. -- The CXX compiler identification is GNU 5.4.0 -- Check for working CXX compiler: /home/user/mxe/usr/bin/i686-w64-mingw32.static-g++ -- Check for working CXX compiler: /home/user/mxe/usr/bin/i686-w64-mingw32.static-g++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/user/cmake-recipes/chapter-13/recipe-01/cxx-example/build
- 现在,让我们构建可执行文件:
$ cmake --build . Scanning dependencies of target hello-world [ 50%] Building CXX object CMakeFiles/hello-world.dir/hello-world.cpp.obj [100%] Linking CXX executable bin/hello-world.exe [100%] Built target hello-world
- 请注意,我们在 Linux 上获得了
hello-world.exe
。将二进制文件复制到 Windows 计算机。 - 在 Windows 计算机上,我们可以观察到以下输出:
Hello from Windows
- 如您所见,该二进制文件在 Windows 上运行!
它是如何工作的
由于我们在与目标环境(Windows)不同的宿主环境(在这种情况下,GNU/Linux 或 macOS)上配置和构建代码,我们需要向 CMake 提供有关目标环境的信息,我们已经在toolchain.cmake
文件中对其进行了编码(cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling
)。
首先,我们提供目标操作系统的名称:
set(CMAKE_SYSTEM_NAME Windows)
然后,我们指定编译器,例如:
set(CMAKE_C_COMPILER i686-w64-mingw32-gcc) set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) set(CMAKE_Fortran_COMPILER i686-w64-mingw32-gfortran)
在这个简单的例子中,我们不需要检测任何库或头文件,但如果需要,我们将使用以下方式指定根路径:
set(CMAKE_FIND_ROOT_PATH /path/to/target/environment)
目标环境可以是例如由 MXE 安装提供的环境。
最后,我们调整 find 命令的默认行为。我们指示 CMake 在目标环境中搜索头文件和库:
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
并在宿主环境中搜索程序:
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
另请参阅
有关各种选项的更详细讨论,请参阅cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling
。
使用 OpenMP 并行化交叉编译 Windows 二进制文件
本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-13/recipe-02
找到,并包含 C++和 Fortran 示例。本食谱适用于 CMake 版本 3.9(及以上),并在 GNU/Linux 上进行了测试。
在本食谱中,我们将应用在前一个食谱中学到的知识,尽管是针对一个更有趣和更现实的例子:我们将交叉编译一个使用 OpenMP 并行化的 Windows 二进制文件。
准备工作
我们将使用第三章,检测外部库和程序,食谱 5,检测 OpenMP 并行环境中的未修改源代码。示例代码计算所有自然数到N的总和(example.cpp
):
#include <iostream> #include <omp.h> #include <string> int main(int argc, char *argv[]) { std::cout << "number of available processors: " << omp_get_num_procs() << std::endl; std::cout << "number of threads: " << omp_get_max_threads() << std::endl; auto n = std::stol(argv[1]); std::cout << "we will form sum of numbers from 1 to " << n << std::endl; // start timer auto t0 = omp_get_wtime(); auto s = 0LL; #pragma omp parallel for reduction(+ : s) for (auto i = 1; i <= n; i++) { s += i; } // stop timer auto t1 = omp_get_wtime(); std::cout << "sum: " << s << std::endl; std::cout << "elapsed wall clock time: " << t1 - t0 << " seconds" << std::endl; return 0; }
CMakeLists.txt
文件与第三章,检测外部库和程序,食谱 5,检测 OpenMP 并行环境相比,基本上没有变化,除了增加了一个安装目标:
# set minimum cmake version cmake_minimum_required(VERSION 3.9 FATAL_ERROR) # project name and language project(recipe-02 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) include(GNUInstallDirs) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) find_package(OpenMP REQUIRED) add_executable(example example.cpp) target_link_libraries(example PUBLIC OpenMP::OpenMP_CXX ) install( TARGETS example DESTINATION ${CMAKE_INSTALL_BINDIR} )
如何操作
通过以下步骤,我们将能够交叉编译一个使用 OpenMP 并行化的 Windows 可执行文件:
- 创建一个目录,其中包含之前列出的
example.cpp
和CMakeLists.txt
。 - 我们将使用与前一个食谱相同的
toolchain.cmake
:
# the name of the target operating system set(CMAKE_SYSTEM_NAME Windows) # which compilers to use set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) # adjust the default behaviour of the find commands: # search headers and libraries in the target environment set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) # search programs in the host environment set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
- 将
CMAKE_CXX_COMPILER
调整为相应的编译器(路径)。 - 然后,通过指向
CMAKE_TOOLCHAIN_FILE
到工具链文件来配置代码(在本例中,使用了从源代码构建的 MXE 编译器):
$ mkdir -p build $ cd build $ cmake -D CMAKE_TOOLCHAIN_FILE=toolchain.cmake .. -- The CXX compiler identification is GNU 5.4.0 -- Check for working CXX compiler: /home/user/mxe/usr/bin/i686-w64-mingw32.static-g++ -- Check for working CXX compiler: /home/user/mxe/usr/bin/i686-w64-mingw32.static-g++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Found OpenMP_CXX: -fopenmp (found version "4.0") -- Found OpenMP: TRUE (found version "4.0") -- Configuring done -- Generating done -- Build files have been written to: /home/user/cmake-recipes/chapter-13/recipe-02/cxx-example/build
- 现在,让我们构建可执行文件:
$ cmake --build . Scanning dependencies of target example [ 50%] Building CXX object CMakeFiles/example.dir/example.cpp.obj [100%] Linking CXX executable bin/example.exe [100%] Built target example
- 将二进制文件
example.exe
复制到 Windows 计算机。 - 在 Windows 计算机上,我们可以看到以下示例输出:
$ set OMP_NUM_THREADS=1 $ example.exe 1000000000 number of available processors: 2 number of threads: 1 we will form sum of numbers from 1 to 1000000000 sum: 500000000500000000 elapsed wall clock time: 2.641 seconds $ set OMP_NUM_THREADS=2 $ example.exe 1000000000 number of available processors: 2 number of threads: 2 we will form sum of numbers from 1 to 1000000000 sum: 500000000500000000 elapsed wall clock time: 1.328 seconds
- 正如我们所见,二进制文件在 Windows 上运行,并且我们可以观察到由于 OpenMP 并行化带来的速度提升!
它是如何工作的
我们已成功使用简单的工具链进行交叉编译,在 Windows 平台上构建了用于并行执行的可执行文件。我们能够通过设置OMP_NUM_THREADS
来指定 OpenMP 线程的数量。从 1 个线程增加到 2 个线程,我们观察到运行时间从 2.6 秒减少到 1.3 秒。有关工具链文件的讨论,请参阅之前的配方。
还有更多
可以为一组目标平台进行交叉编译,例如 Android。有关示例,我们请读者参考cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html
。
第十五章:测试仪表板
在本章中,我们将介绍以下内容:
- 将测试部署到 CDash 仪表板
- 向 CDash 仪表板报告测试覆盖率
- 使用 AddressSanitizer 并向 CDash 报告内存缺陷
- 使用 ThreadSanitizer 并向 CDash 报告数据竞争
引言
CDash 是一个 Web 服务,用于聚合 CTest 在测试运行、夜间测试或在持续集成设置中报告的测试结果。向仪表板报告就是我们所说的CDash 时间,如下图所示:
在本章中,我们将演示如何向 CDash 仪表板报告测试结果。我们将讨论报告测试覆盖率的策略,以及使用 AddressSanitizer 和 ThreadSanitizer 等工具收集的内存缺陷和数据竞争。
向 CDash 报告有两种方式:通过构建的测试目标或使用 CTest 脚本。我们将在前两个食谱中演示测试目标的方法,并在最后两个食谱中使用 CTest 脚本的方法。
设置 CDash 仪表板
CDash 的安装需要一个带有 PHP 和 SSL 启用的 Web 服务器(Apache、NGINX 或 IIS),以及访问 MySQL 或 PostgreSQL 数据库服务器的权限。本书不详细讨论 CDash Web 服务的设置;我们建议读者参考其官方文档,网址为public.kitware.com/Wiki/CDash:Installation
。
安装 CDash 实例不是本章食谱的必要条件,因为 Kitware 提供了两个公共仪表板(my.cdash.org
和open.cdash.org
)。我们将在食谱中引用前者。
对于决定自行安装 CDash 实例的读者,我们建议使用 MySQL 后端,因为这似乎是my.cdash.org
和open.cdash.org
所使用的配置,并且社区对其进行了更充分的测试。
也可以使用 Docker 来部署 CDash 实例,而无需太多努力。目前,在 CDash 问题跟踪器上有一个关于官方镜像的请求,网址为github.com/Kitware/CDash/issues/562
。
将测试部署到 CDash 仪表板
本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-14/recipe-01
找到,并包含一个 C++示例。该食谱适用于 CMake 版本 3.5(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。
在本食谱中,我们将扩展第 1 个食谱,即“创建一个简单的单元测试”,来自第四章,“创建和运行测试”,并将测试结果部署到my.cdash.org/index.php?project=cmake-cookbook
,这是我们在公共仪表板(my.cdash.org
)上为本书创建的,由 Kitware 提供给社区。
准备工作
我们将从重用第 1 个食谱,即“创建一个简单的单元测试”,来自第四章,“创建和运行测试”的示例源代码开始,该示例对作为命令行参数给出的整数求和。示例由三个源文件组成:main.cpp
、sum_integers.cpp
和sum_integers.hpp
。这些源文件保持不变。我们还将重用来自第四章,“创建和运行测试”的文件test.cpp
,但将其重命名为test_short.cpp
。我们将通过test_long.cpp
扩展示例,其中包含以下代码:
#include "sum_integers.hpp" #include <numeric> #include <vector> int main() { // creates vector {1, 2, 3, ..., 999, 1000} std::vector<int> integers(1000); std::iota(integers.begin(), integers.end(), 1); if (sum_integers(integers) == 500500) { return 0; } else { return 1; } }
然后,我们将这些文件组织成以下文件树:
. ├── CMakeLists.txt ├── CTestConfig.cmake ├── src │ ├── CMakeLists.txt │ ├── main.cpp │ ├── sum_integers.cpp │ └── sum_integers.hpp └── tests ├── CMakeLists.txt ├── test_long.cpp └── test_short.cpp
如何做到这一点
现在,我们将描述如何配置、构建、测试,最后,将我们示例项目的测试结果提交到仪表板:
- 源目标在
src/CMakeLists.txt
中定义,如下所示:
# example library add_library(sum_integers "") target_sources(sum_integers PRIVATE sum_integers.cpp PUBLIC ${CMAKE_CURRENT_LIST_DIR}/sum_integers.hpp ) target_include_directories(sum_integers PUBLIC ${CMAKE_CURRENT_LIST_DIR} ) # main code add_executable(sum_up main.cpp) target_link_libraries(sum_up sum_integers)
- 测试在
tests/CMakeLists.txt
中定义:
add_executable(test_short test_short.cpp) target_link_libraries(test_short sum_integers) add_executable(test_long test_long.cpp) target_link_libraries(test_long sum_integers) add_test( NAME test_short COMMAND $<TARGET_FILE:test_short> ) add_test( NAME test_long COMMAND $<TARGET_FILE:test_long> )
- 顶级
CMakeLists.txt
文件引用了前面两个文件,本食谱中的新元素是包含include(CTest)
的行,它允许我们向 CDash 仪表板报告:
# set minimum cmake version cmake_minimum_required(VERSION 3.5 FATAL_ERROR) # project name and language project(recipe-01 LANGUAGES CXX) # require C++11 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) # process src/CMakeLists.txt add_subdirectory(src) enable_testing() # allow to report to a cdash dashboard include(CTest) # process tests/CMakeLists.txt add_subdirectory(tests)
- 此外,我们在顶级
CMakeLists.txt
文件所在的同一目录中创建了文件CTestConfig.cmake
。这个新文件包含以下行:
set(CTEST_DROP_METHOD "http") set(CTEST_DROP_SITE "my.cdash.org") set(CTEST_DROP_LOCATION "/submit.php?project=cmake-cookbook") set(CTEST_DROP_SITE_CDASH TRUE)
- 我们现在准备配置并构建项目,如下所示:
$ mkdir -p build $ cd build $ cmake .. $ cmake --build .
- 在构建代码之后,我们可以运行测试集并将测试结果报告给仪表板:
$ ctest --dashboard Experimental Site: larry Build name: Linux-c++ Create new tag: 20180408-1449 - Experimental Configure project Each . represents 1024 bytes of output . Size of output: 0K Build project Each symbol represents 1024 bytes of output. '!' represents an error and '*' a warning. . Size of output: 0K 0 Compiler errors 0 Compiler warnings Test project /home/user/cmake-recipes/chapter-15/recipe-01/cxx-example/build Start 1: test_short 1/2 Test #1: test_short ....................... Passed 0.00 sec Start 2: test_long 2/2 Test #2: test_long ........................ Passed 0.00 sec 100% tests passed, 0 tests failed out of 2 Total Test time (real) = 0.01 sec Performing coverage Cannot find any coverage files. Ignoring Coverage request. Submit files (using http) Using HTTP submit method Drop site:http://my.cdash.org/submit.php?project=cmake-cookbook Uploaded: /home/user/cmake-recipes/chapter-14/recipe-01/cxx-example/build/Testing/20180408-1449/Build.xml Uploaded: /home/user/cmake-recipes/chapter-14/recipe-01/cxx-example/build/Testing/20180408-1449/Configure.xml Uploaded: /home/user/cmake-recipes/chapter-14/recipe-01/cxx-example/build/Testing/20180408-1449/Test.xml Submission successful
- 最后,我们可以在浏览器中浏览测试结果(在本例中,测试结果被报告给
my.cdash.org/index.php?project=cmake-cookbook
):)
CMake 秘籍(七)(4)https://developer.aliyun.com/article/1525427