CMake 秘籍(六)(4)https://developer.aliyun.com/article/1525061
还有更多
对于将包上传到 PyPI 测试和生产实例的后续步骤,我们请读者参考之前的食谱,因为这些步骤是类似的。
将一个简单项目作为 Conda 包分发
本节的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-11/recipe-04
找到。本节适用于 CMake 版本 3.5(及更高版本),并在 GNU/Linux、macOS 和 Windows 上进行了测试。
尽管 PyPI 是分发 Python 包的标准且流行的平台,但 Anaconda(anaconda.org
)更为通用,因为它不仅允许分发带有 Python 接口的 Python 或混合语言项目,还允许为非 Python 项目进行打包和依赖管理。在本节中,我们将为使用 CMake 配置和构建的非常简单的 C++示例项目准备一个 Conda 包,该项目没有除 C++之外的其他依赖项。在下一节中,我们将准备并讨论一个更复杂的 Conda 包。
准备就绪
我们的目标将是打包以下简单的示例代码(example.cpp
):
#include <iostream> int main() { std::cout << "hello from your conda package!" << std::endl; return 0; }
如何操作
以下是按步骤进行的方法:
CMakeLists.txt
文件以最小版本要求、项目名称和支持的语言开始:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(recipe-04 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON)
- 我们希望构建
hello-conda
可执行文件,该文件由example.cpp
构建:
add_executable(hello-conda "") target_sources(hello-conda PRIVATE example.cpp )
- 我们通过定义安装目标来结束
CMakeLists.txt
:
install( TARGETS hello-conda DESTINATION bin )
- 我们将在名为
meta.yaml
的文件中描述 Conda 包,我们将把它放在conda-recipe
下,以达到以下文件结构:
. ├── CMakeLists.txt ├── conda-recipe │ └── meta.yaml └── example.cpp
meta.yaml
文件由以下内容组成:
package: name: conda-example-simple version: "0.0.0" source: path: ../ # this can be changed to git-url build: number: 0 binary_relocation: true script: - cmake -H. -Bbuild_conda -G "${CMAKE_GENERATOR}" -DCMAKE_INSTALL_PREFIX=${PREFIX} # [not win] - cmake -H. -Bbuild_conda -G "%CMAKE_GENERATOR%" -DCMAKE_INSTALL_PREFIX="%LIBRARY_PREFIX%" # [win] - cmake --build build_conda --target install requirements: build: - cmake >=3.5 - {{ compiler('cxx') }} about: home: http://www.example.com license: MIT summary: "Summary in here ..."
- 现在我们可以尝试构建包:
$ conda build conda-recipe
- 我们将在屏幕上看到大量输出,但一旦构建完成,我们就可以安装包。我们将首先进行本地安装:
$ conda install --use-local conda-example-simple
- 现在我们准备测试它——打开一个新的终端(假设已激活 Anaconda)并输入以下内容:
$ hello-conda hello from your conda package!
- 测试成功后,我们可以再次删除该包:
$ conda remove conda-example-simple
工作原理
CMakeLists.txt
中的安装目标是本节的关键组件:
install( TARGETS hello-conda DESTINATION bin )
此目标确保二进制文件安装在${CMAKE_INSTALL_PREFIX}/bin
中。前缀变量在meta.yaml
的构建步骤中由 Conda 定义:
build: number: 0 binary_relocation: true script: - cmake -H. -Bbuild_conda -G "${CMAKE_GENERATOR}" -DCMAKE_INSTALL_PREFIX=${PREFIX} # [not win] - cmake -H. -Bbuild_conda -G "%CMAKE_GENERATOR%" -DCMAKE_INSTALL_PREFIX="%LIBRARY_PREFIX%" # [win] - cmake --build build_conda --target install
构建步骤配置项目,将安装前缀设置为${PREFIX}
(由 Conda 设置的内在变量),构建并安装项目。将构建目录命名为build_conda
的动机与前面的节类似:特定的构建目录名称使得更容易基于可能已经包含名为build
的目录的目录进行本地安装实验。
通过将包安装到 Anaconda 环境中,我们使可执行文件对系统可用。
还有更多
配置文件meta.yaml
可用于指定项目的构建、测试和安装步骤,原则上任何复杂度的项目都可以使用。请参考官方文档进行深入讨论:conda.io/docs/user-guide/tasks/build-packages/define-metadata.html
。
要将 Conda 包上传到 Anaconda 云,请遵循 Anaconda 云官方文档:docs.anaconda.com/anaconda-cloud/user-guide/
。同时,可以考虑使用 Miniconda 作为 Anaconda 的轻量级替代品:conda.io/miniconda.html
。
以 Conda 包形式分发具有依赖关系的项目
本食谱的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-11/recipe-05
找到。该食谱适用于 CMake 版本 3.5(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。
在本食谱中,我们将基于前一个食谱的发现,为示例 CMake 项目准备一个更真实、更复杂的 Conda 包,该项目将依赖于并利用 Intel 数学内核库(MKL)中提供的 DGEMM 函数实现,用于矩阵乘法。Intel MKL 作为 Conda 包提供。本食谱将为我们提供准备和共享具有依赖关系的 Conda 包的工具集。
准备就绪
对于本食谱,我们将使用与前一个简单 Conda 食谱相同的文件命名和目录结构:
. ├── CMakeLists.txt ├── conda-recipe │ └── meta.yaml └── example.cpp
示例源文件(example.cpp
)执行矩阵乘法,并将 MKL 库返回的结果与“noddy”实现进行比较:
#include "mkl.h" #include <cassert> #include <cmath> #include <iostream> #include <random> int main() { // generate a uniform distribution of real number between -1.0 and 1.0 std::random_device rd; std::mt19937 mt(rd()); std::uniform_real_distribution<double> dist(-1.0, 1.0); int m = 500; int k = 1000; int n = 2000; double *A = (double *)mkl_malloc(m * k * sizeof(double), 64); double *B = (double *)mkl_malloc(k * n * sizeof(double), 64); double *C = (double *)mkl_malloc(m * n * sizeof(double), 64); double *D = new double[m * n]; for (int i = 0; i < (m * k); i++) { A[i] = dist(mt); } for (int i = 0; i < (k * n); i++) { B[i] = dist(mt); } for (int i = 0; i < (m * n); i++) { C[i] = 0.0; } double alpha = 1.0; double beta = 0.0; cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, m, n, k, alpha, A, k, B, n, beta, C, n); // D_mn = A_mk B_kn for (int r = 0; r < m; r++) { for (int c = 0; c < n; c++) { D[r * n + c] = 0.0; for (int i = 0; i < k; i++) { D[r * n + c] += A[r * k + i] * B[i * n + c]; } } } // compare the two matrices double r = 0.0; for (int i = 0; i < (m * n); i++) { r += std::pow(C[i] - D[i], 2.0); } assert(r < 1.0e-12 && "ERROR: matrices C and D do not match"); mkl_free(A); mkl_free(B); mkl_free(C); delete[] D; std::cout << "MKL DGEMM example worked!" << std::endl; return 0; }
我们还需要一个修改过的meta.yaml
。但是,与前一个食谱相比,唯一的更改是在需求下添加了mkl-devel
依赖项的行:
package: name: conda-example-dgemm version: "0.0.0" source: path: ../ # this can be changed to git-url build: number: 0 script: - cmake -H. -Bbuild_conda -G "${CMAKE_GENERATOR}" -DCMAKE_INSTALL_PREFIX=${PREFIX} # [not win] - cmake -H. -Bbuild_conda -G "%CMAKE_GENERATOR%" -DCMAKE_INSTALL_PREFIX="%LIBRARY_PREFIX%" # [win] - cmake --build build_conda --target install requirements: build: - cmake >=3.5 - {{ compiler('cxx') }} host: - mkl-devel 2018 about: home: http://www.example.com license: MIT summary: "Summary in here ..."
如何操作
以下是准备我们包的步骤:
CMakeLists.txt
文件以最小版本要求、项目名称和支持的语言开始:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(recipe-05 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON)
- 我们希望构建
dgemm-example
可执行文件,它由example.cpp
构建:
add_executable(dgemm-example "") target_sources(dgemm-example PRIVATE example.cpp )
- 然后,我们需要定位通过
mkl-devel
安装的 MKL 库。我们准备了一个名为IntelMKL
的INTERFACE
库。这可以像任何其他目标一样使用,并将为任何依赖目标设置包含目录、编译器选项和链接库。设置旨在模仿 Intel MKL 链接行顾问建议的内容(software.intel.com/en-us/articles/intel-mkl-link-line-advisor/
)。首先,我们设置编译器选项:
add_library(IntelMKL INTERFACE) target_compile_options(IntelMKL INTERFACE $<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:AppleClang>>:-m64> )
- 接下来,我们搜索
mkl.h
头文件,并为IntelMKL
目标设置include
目录:
find_path(_mkl_h NAMES mkl.h HINTS ${CMAKE_INSTALL_PREFIX}/include ) target_include_directories(IntelMKL INTERFACE ${_mkl_h} ) message(STATUS "MKL header file FOUND: ${_mkl_h}")
- 最后,我们定位库并设置
IntelMKL
目标的链接库:
find_library(_mkl_libs NAMES mkl_rt HINTS ${CMAKE_INSTALL_PREFIX}/lib ) message(STATUS "MKL single dynamic library FOUND: ${_mkl_libs}") find_package(Threads QUIET) target_link_libraries(IntelMKL INTERFACE ${_mkl_libs} $<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:AppleClang>>:Threads::Threads> $<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:AppleClang>>:m> )
- 我们使用
cmake_print_properties
函数打印有关IntelMKL
目标的有用消息:
include(CMakePrintHelpers) cmake_print_properties( TARGETS IntelMKL PROPERTIES INTERFACE_COMPILE_OPTIONS INTERFACE_INCLUDE_DIRECTORIES INTERFACE_LINK_LIBRARIES )
- 我们将
dgemm-example
目标与这些库链接:
target_link_libraries(dgemm-example PRIVATE IntelMKL )
- 我们通过定义安装目标来结束
CMakeLists.txt
:
install( TARGETS dgemm-example DESTINATION bin )
- 现在我们可以尝试构建包:
$ conda build conda-recipe
- 我们将在屏幕上看到大量输出,但一旦构建完成,我们就可以安装该包。我们将首先在本地进行此操作:
$ conda install --use-local conda-example-dgemm
- 现在我们准备测试它 – 打开一个新的终端(假设 Anaconda 已激活)并输入:
$ dgemm-example MKL DGEMM example worked!
- 测试成功后,我们可以再次删除该包:
$ conda remove conda-example-dgemm
它是如何工作的
与之前的配方相比,meta.yaml
中的唯一变化是mkl-devel
依赖项。从 CMake 的角度来看,挑战在于定位 Anaconda 安装的 MKL 库。幸运的是,我们知道它们位于${CMAKE_INSTALL_PREFIX}
。在线提供的 Intel MKL 链接行顾问(software.intel.com/en-us/articles/intel-mkl-link-line-advisor/
)可以用来查找根据所选平台和编译器将 MKL 链接到我们项目的方法。我们决定将这些信息封装成一个INTERFACE
库。对于 MKL 的情况,这种解决方案是理想的:该库不是我们项目或任何子项目创建的目标,但它仍然需要以可能非常复杂的方式处理;即:设置编译器标志、包含目录和链接库。CMake INTERFACE
库是构建系统中的目标,但不直接创建任何构建输出。然而,由于它们是目标,我们可以在它们上面设置属性。就像“真实”目标一样,它们也可以被安装、导出和导入。
首先,我们声明一个名为IntelMKL
的新库,并带有INTERFACE
属性。然后我们需要根据需要设置属性,我们遵循在目标上使用INTERFACE
属性调用适当的 CMake 命令的模式,使用以下命令:
target_compile_options
,用于设置INTERFACE_COMPILE_OPTIONS
。在我们的例子中,必须设置-m64
,但仅在使用 GNU 或 AppleClang 编译器时。请注意,我们使用生成器表达式来执行此操作。target_include_directories
,用于设置INTERFACE_INCLUDE_DIRECTORIES
。这些可以在系统中找到mkl.h
头文件后设置。这是通过find_path
CMake 命令完成的。target_link_libraries
,用于设置INTERFACE_LINK_LIBRARIES
。我们决定链接到单个动态库libmkl_rt.so
,并使用find_library
CMake 命令搜索它。GNU 或 AppleClang 编译器还需要将可执行文件链接到本机线程和数学库。再次,这些情况都优雅地使用生成器表达式处理。
我们刚刚在 IntelMKL 目标上设置的属性可以通过cmake_print_properties
命令打印出来供用户查看。最后,我们链接到IntelMKL
目标。正如预期的那样,这将设置编译器标志、包含目录和链接库,以确保成功编译:
target_link_libraries(dgemm-example PRIVATE IntelMKL )
还有更多
Anaconda 云包含大量包。有了前面的配方,就有可能并且相对简单地为可能依赖于其他 Conda 包的 CMake 项目构建 Conda 包。探索这种可能性,并分享您的软件包,以便其他人可以在您的开发基础上构建!