# 11.5 将Conda包作为依赖项发布给项目

**NOTE**:*此示例代码可以在* <https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-11/recipe-05> *中找到。该示例在CMake 3.5版(或更高版本)中是有效的，并且已经在GNU/Linux、macOS和Windows上进行过测试。*

这个示例中，我们将基于之前示例的结果，并且为CMake项目准备一个更真实和复杂的Conda包，这将取决于DGEMM的函数实现，对于矩阵与矩阵的乘法，可以使用Intel的MKL库进行。Intel的MKL库可以以Conda包的形式提供。此示例将为我们提供一个工具集，用于准备和共享具有依赖关系的Conda包。

## 准备工作

对于这个示例，我们将使用与前一个示例中的Conda配置，和相同的文件命名和目录结构：

```
.
├── CMakeLists.txt
├── conda-recipe
│    └── meta.yaml
└── example.cpp
```

示例文件(`example.cpp`)将执行矩阵-矩阵乘法，并将MKL库返回的结果与“noddy”实现进行比较:

```cpp
#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`：

```yaml
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)
   ```
2. 使用`example.cpp`构建`dgem-example`可执行目标：

   ```
   add_executable(dgemm-example "")
   target_sources(dgemm-example
     PRIVATE
         example.cpp
     )
   ```
3. 然后，需要找到通过`MKL-devel`安装的MKL库。我们准备了一个名为`IntelMKL`的`INTERFACE`库，该库可以用于其他目标，并将为依赖的目标设置包括目录、编译器选项和链接库。根据Intel的建议(<https://software.intel.com/en-us/articles/intel-mml-link-line-advisor/> )进行设置。首先，设置编译器选项：

   ```
   add_library(IntelMKL INTERFACE)

   target_compile_options(IntelMKL
     INTERFACE
         $<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:AppleClang>>:-m64>
     )
   ```
4. 接下来，查找`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}")
   ```
5. 最后，为`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>
     )
   ```
6. 使用`cmake_print_properties`函数，打印`IntelMKL`目标的信息：

   ```
   include(CMakePrintHelpers)
   cmake_print_properties(
     TARGETS
         IntelMKL
     PROPERTIES
       INTERFACE_COMPILE_OPTIONS
       INTERFACE_INCLUDE_DIRECTORIES
       INTERFACE_LINK_LIBRARIES
     )
   ```
7. 将这些库连接到`dgem-example`:

   ```
   target_link_libraries(dgemm-example
     PRIVATE
         IntelMKL
     )
   ```
8. `CMakeLists.txt`中定义了安装目标:

   ```
   install(
     TARGETS
         dgemm-example
     DESTINATION
         bin
     )
   ```
9. 尝试构建包：

   ```
   $ conda build conda-recipe
   ```
10. 过程中屏幕上将看到大量输出，但是一旦构建完成，就可以对包进行安装包。首先，在本地进行安装测试：

    ```
    $ conda install --use-local conda-example-dgemm
    ```
11. 现在测试安装，打开一个新的终端(假设Anaconda处于激活状态)，并输入：

    ```
    $ dgemm-example

    MKL DGEMM example worked!
    ```
12. 安装成功之后，再进行卸载：

    ```
    $ conda remove conda-example-dgemm
    ```

## 工作原理

`meta.yaml`中的变化就是`mml-devel`依赖项。从CMake的角度来看，这里的挑战是定位Anaconda安装的MKL库。幸运的是，我们知道它位于`${CMAKE_INSTALL_PREFIX}`中。可以使用在线的`Intel MKL link line advisor`(<https://software.intel.com/en-us/articles/intel-mml-link-line-advisor/>) 查看如何根据选择的平台和编译器，将MKL链接到我们的项目中，我们会将此信息封装到`INTERFACE`库中。这个解决方案非常适合类MKL的情况：库不是由我们的项目或任何子项目创建的目标，但是它仍然需要以一种方式进行处理；也就是：设置编译器标志，包括目录和链接库。`INTERFACE`库是构建系统中的目标，但不创建任何构建输出(至少不会直接创建)。但由于它们是目标，我们可对它们的属性进行设置。这样与“实际”目标一样，可以安装、导出和导入。

首先，我们用`INTERFACE`属性声明一个名为`IntelMKL`的新库。然后，根据需要设置属性，并使用`INTERFACE`属性在目标上调用适当的CMake命令：

* target\_compile\_options：用于设置`INTERFACE_COMPILE_OPTIONS`。示例中，设置了`-m64`，不过这个标志只有GNU和AppleClange编译器能够识别。并且，我们使用生成器表达式来实现。
* target\_include\_directories：用于设置`INTERFACE_INCLUDE_DIRECTORIES`。使用`find_path`，可以在找到系统上的`mkl.h`头文件后设置这些参数。
* target\_link\_libraries：用于设置`INTERFACE_LINK_LIBRARIES`。我们决定链接动态库`libmkl_rt.so`，并用`find_library`搜索它。GNU或AppleClang编译器还需要将可执行文件链接到线程和数学库。同样，这些情况可以使用生成器表达式优雅地进行处理。

在`IntelMKL`目标上设置的属性后，可以通过`cmake_print_properties`命令将属性进行打印。最后，链接到`IntelMKL`目标，这将设置编译器标志，包括目录和链接库：

```
target_link_libraries(dgemm-example
  PRIVATE
      IntelMKL
  )
```

## 更多信息

Anaconda云上包含大量包。使用上述方法，可以为CMake项目构建依赖于其他Conda包的Conda包。这样，就可以探索软件功能的各种可能性，并与他人分享您的软件包!


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenxiaowei.gitbook.io/cmake-cookbook/11.0-chinese/11.5-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
