CMake 秘籍(六)(5)

简介: CMake 秘籍(六)

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;
}

如何操作

以下是按步骤进行的方法:

  1. 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)
  1. 我们希望构建hello-conda可执行文件,该文件由example.cpp构建:
add_executable(hello-conda "")
target_sources(hello-conda
  PRIVATE
    example.cpp
  )
  1. 我们通过定义安装目标来结束CMakeLists.txt
install(
  TARGETS
    hello-conda
  DESTINATION
    bin
  )
  1. 我们将在名为meta.yaml的文件中描述 Conda 包,我们将把它放在conda-recipe下,以达到以下文件结构:
.
├── CMakeLists.txt
├── conda-recipe
│   └── meta.yaml
└── example.cpp
  1. 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 ..."
  1. 现在我们可以尝试构建包:
$ conda build conda-recipe
  1. 我们将在屏幕上看到大量输出,但一旦构建完成,我们就可以安装包。我们将首先进行本地安装:
$ conda install --use-local conda-example-simple
  1. 现在我们准备测试它——打开一个新的终端(假设已激活 Anaconda)并输入以下内容:
$ hello-conda 
hello from your conda package!
  1. 测试成功后,我们可以再次删除该包:
$ 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 ..."

如何操作

以下是准备我们包的步骤:

  1. 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)
  1. 我们希望构建dgemm-example可执行文件,它由example.cpp构建:
add_executable(dgemm-example "")
target_sources(dgemm-example
  PRIVATE
    example.cpp
  )
  1. 然后,我们需要定位通过mkl-devel安装的 MKL 库。我们准备了一个名为IntelMKLINTERFACE库。这可以像任何其他目标一样使用,并将为任何依赖目标设置包含目录、编译器选项和链接库。设置旨在模仿 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>
  )
  1. 接下来,我们搜索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}")
  1. 最后,我们定位库并设置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>
  )
  1. 我们使用cmake_print_properties函数打印有关IntelMKL目标的有用消息:
include(CMakePrintHelpers)
cmake_print_properties(
  TARGETS
    IntelMKL
  PROPERTIES
    INTERFACE_COMPILE_OPTIONS
    INTERFACE_INCLUDE_DIRECTORIES
    INTERFACE_LINK_LIBRARIES
  )
  1. 我们将dgemm-example目标与这些库链接:
target_link_libraries(dgemm-example
  PRIVATE
    IntelMKL
  )
  1. 我们通过定义安装目标来结束CMakeLists.txt
install(
  TARGETS
    dgemm-example
  DESTINATION
    bin
  )
  1. 现在我们可以尝试构建包:
$ conda build conda-recipe
  1. 我们将在屏幕上看到大量输出,但一旦构建完成,我们就可以安装该包。我们将首先在本地进行此操作:
$ conda install --use-local conda-example-dgemm
  1. 现在我们准备测试它 – 打开一个新的终端(假设 Anaconda 已激活)并输入:
$ dgemm-example 
MKL DGEMM example worked!
  1. 测试成功后,我们可以再次删除该包:
$ 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 包。探索这种可能性,并分享您的软件包,以便其他人可以在您的开发基础上构建!

相关文章
|
6月前
|
编译器 Shell
CMake 秘籍(八)(3)
CMake 秘籍(八)
39 2
|
6月前
|
编译器 Shell 开发工具
CMake 秘籍(八)(5)
CMake 秘籍(八)
36 2
|
6月前
|
消息中间件 Unix C语言
CMake 秘籍(二)(5)
CMake 秘籍(二)
131 1
|
6月前
|
Linux 编译器 C++
CMake 秘籍(七)(2)
CMake 秘籍(七)
43 1
|
6月前
|
Linux iOS开发 C++
CMake 秘籍(六)(3)
CMake 秘籍(六)
47 1
|
6月前
|
Shell Linux C++
CMake 秘籍(六)(4)
CMake 秘籍(六)
49 1
|
6月前
|
Linux API iOS开发
CMake 秘籍(六)(1)
CMake 秘籍(六)
44 1
|
6月前
|
编译器 开发工具
CMake 秘籍(八)(2)
CMake 秘籍(八)
31 0
|
6月前
|
并行计算 Unix 编译器
CMake 秘籍(七)(5)
CMake 秘籍(七)
85 0
|
6月前
|
Linux C++ iOS开发
CMake 秘籍(七)(1)
CMake 秘籍(七)
31 0