构建变体#

二进制兼容性(和不兼容性)的本质意味着我们有时需要构建二进制包(以及任何包含二进制文件的包)的几个变体,以支持不同的使用环境。例如,使用 NumPy 的 C API 意味着包必须在运行时使用与构建时相同的 NumPy 版本。

长期以来,对此的支持有限。在构建和运行要求中包含 Python 会导致一个包,其中 Python 固定到构建时使用的 Python 版本,以及对文件名的相应添加,例如“py27”。类似的支持存在于 NumPy 中,在 Conda-build PR 573 合并后,在食谱后添加了 x.x 固定。在 conda-build 3.0 之前,还有许多针对一般支持的长期提议 (Conda-build 问题 1142)。

从 conda-build 3.0 开始,添加了一种新的配置方案,称为“变体”。从概念上讲,这将固定值与食谱分离,将它们替换为 Jinja2 模板变量。它增加了对“兼容”固定概念的支持,以与 ABI 兼容性数据库(例如 ABI 实验室)集成。请注意,“兼容”固定概念目前仍在积极开发中。

变体输入最终是一个字典。这些字典大多非常扁平。键直接在 Jinja2 模板中可用。因此,字典中的键(以及读入字典的文件)必须是有效的 jinja2 变量名(不允许使用 - 字符)。此示例在一个构建命令中构建 Python 2.7 和 3.5 包

conda_build_config.yaml 类似

python:
    - 2.7
    - 3.5

meta.yaml 内容类似

package:
    name: compiled-code
    version: 1.0

requirements:
    build:
        - python
    run:
        - python

与早期的 conda-build 版本相比,构建食谱的命令保持不变。例如,如果我们的 shell 与 meta.yaml 和 conda_build_config.yaml 位于同一个文件夹中,我们只需调用 conda build . 命令。

一般固定示例#

固定有一些特征用例。请将此视为下面内容的地图。

  1. 提供二进制接口的共享库。所有对该库的使用都使用二进制接口。将相同的固定应用于所有构建很方便。示例:boost

    您 HOME 文件夹中的 conda_build_config.yaml

    boost:
      - 1.61
      - 1.63
    pin_run_as_build:
      boost: x.x
    

    meta.yaml

    package:
        name: compiled-code
        version: 1.0
    
    requirements:
        build:
            - boost
        run:
            - boost
    

    此示例演示了几个功能

    • 使用特定命名的配置文件进行用户范围的配置(您主文件夹中的 conda_build_config.yaml)。更多选项,请参见下面的 创建 conda-build 变体配置文件

    • 针对单个库的多个版本构建(在构建时设置已安装的版本)。

    • 将运行时要求固定到构建时使用的版本。更多信息,请参见下面的 在变体级别固定

    • 指定固定的粒度。 x.x 固定主版本和次版本。更多信息,请参见 固定表达式

  2. 具有外部可访问二进制组件的 Python 包。并非所有对该库的使用都使用二进制接口(有些只使用纯 Python)。示例:NumPy。

    您食谱文件夹(与 meta.yaml 位于同一文件夹中)中的 conda_build_config.yaml

    numpy:
      - 1.11
      - 1.12
    

    meta.yaml

    package:
        name: numpy_using_pythonAPI_thing
        version: 1.0
    
    requirements:
        build:
            - python
            - numpy
        run:
            - python
            - numpy
    

    此示例演示了一个特定功能:减少固定不必要的构建。由于上面的示例食谱只需要 NumPy 的 Python API,因此我们只构建一次包,并且 NumPy 的版本在运行时不会固定为与编译时版本匹配。更多信息,请参见下面的 避免不必要的构建

    对于使用 NumPy C API 的另一个包,我们需要在这个食谱(并且只在这个食谱中)实际固定 NumPy(以便其他食谱不会不必要地构建大量变体)。要固定 NumPy,您可以在 meta.yaml 中直接使用变体键

    package:
        name: numpy_using_cAPI_thing
        version: 1.0
    
    requirements:
        build:
            - numpy
        run:
            - numpy
    

    为了向后兼容,Python 会在您的食谱中没有指定 {{ python }} 的情况下隐式固定。这通常难以扩展到所有包名称,因此一般来说,请养成习惯,始终使用 Jinja2 变量替换,使用您 conda_build_config.yaml 文件中的版本进行固定。

    还有更灵活的方法可以使用 固定表达式 进行固定。有关示例,请参见 在食谱级别固定

  3. 一个食谱拆分为多个包,并且包依赖项需要在彼此之间动态固定。示例:GCC/libgcc/libstdc++/gfortran/等。

    动态固定是棘手的部分。Conda-build 提供了新的方法来引用单个食谱中的其他子包。

    package:
        name: dynamic_supackage
        version: 1.0
    
    requirements:
        run:
            - {{ pin_subpackage('my_awesome_subpackage') }}
    
    outputs:
      - name: my_awesome_subpackage
        version: 2.0
    

    通过以这种方式引用子包,您无需担心 my_awesome_subpackage 的最终版本是什么。独立更新它,只需让 conda-build 找出并保持一致即可。更多信息,请参见下面的 引用子包 部分。

过渡指南#

假设我们有一组食谱,当前构建一个 C 库,以及该 C 库的 Python 和 R 绑定。xgboost 是一个最近的机器学习库,就是一个这样的例子。在 conda-build 2.0 及更早版本中,您需要拥有 3 个食谱 - 每个组件 1 个。让我们回顾一些简化的 meta.yaml 文件。首先是 C 库

package:
    name: libxgboost
    version: 1.0

接下来是 Python 绑定

package:
    name: py-xgboost
    version: 1.0

requirements:
    build:
        - libxgboost  # you probably want to pin the version here, but there's no dynamic way to do it
        - python
    run:
        - libxgboost  # you probably want to pin the version here, but there's no dynamic way to do it
        - python
package:
    name: r-xgboost
    version: 1.0

requirements:
    build:
        - libxgboost  # you probably want to pin the version here, but there's no dynamic way to do it
        - r-base
    run:
        - libxgboost  # you probably want to pin the version here, but there's no dynamic way to do it
        - r-base

要构建这些,您需要几个 conda-build 命令,或者像 conda-build-all 这样的工具来构建各种 Python 版本。使用 conda-build 3.0 和来自 conda-build 2.1 的拆分包,我们可以将其简化为一个连贯的食谱,其中还包含所有所需 Python 和 R 构建的矩阵。

首先,是 meta.yaml 文件

package:
    name: xgboost
    version: 1.0

outputs:
    - name: libxgboost
    - name: py-xgboost
      requirements:
          - {{ pin_subpackage('libxgboost', exact=True) }}
          - python

    - name: r-xgboost
      requirements:
          - {{ pin_subpackage('libxgboost', exact=True) }}
          - r-base

接下来,是 conda_build_config.yaml 文件,指定我们的构建矩阵

python:
    - 2.7
    - 3.5
    - 3.6
r_base:
    - 3.3.2
    - 3.4.0

使用这种更新的方法,您将获得一个完整的构建矩阵:总共 6 个构建。一个 libxgboost 库、3 个 Python 版本和 2 个 R 版本。此外,Python 和 R 包将与该食谱构建的 libxgboost 包具有精确的固定。

创建 conda-build 变体配置文件#

变体输入文件是 yaml 文件。搜索这些文件的顺序如下

  1. 用户 HOME 文件夹中名为 conda_build_config.yaml 的文件(或在 .condarc 文件中为 conda_build/config_file 键指定的任意命名的文件)。

  2. 当前工作目录中名为 conda_build_config.yaml 的文件。

  3. 与您的食谱的 meta.yaml 位于同一个文件夹中,名为 conda_build_config.yaml 的文件。

  4. 使用 --variant-config-files-m 命令行标志在命令行上指定的任何其他文件,这些标志可以多次传递以指定多个文件。 conda buildconda render 命令接受这些参数。

在此搜索顺序中稍后找到的文件中的值将覆盖和替换来自更早文件的值。

注意

conda_build/config_file 是一个嵌套值

conda_build:
  config_file: some/path/to/file

使用 conda-build API 的变体#

最终,变体只是一个字典。此字典直接提供给 Jinja2,您可以在 Jinja2 模板中使用您变体配置中声明的任何键。您可以通过两种方式将这些信息馈送到 API 中

  1. variants 关键字参数传递给 API 函数。目前,buildrenderget_output_file_pathscheck 函数接受此参数。 variants 应该是一个字典,其中每个值都是要迭代的版本列表。这些按以下 多个变体的聚合 部分中所述进行汇总。

  2. 设置 Config 对象的 variant 成员。这只是一个字典。字段的值应为字符串或字符串列表,除了“扩展键”,它们在下面的 扩展键 部分中有所介绍。

同样,使用 meta.yaml 内容类似

package:
    name: compiled-code
    version: 1.0

requirements:
    build:
        - python
    run:
        - python

您可以像这样提供变体来构建此食谱

variants = {"python": ["2.7", "3.5"]}
api.build(path_to_recipe, variants=variants)

请注意,这些 Jinja2 变量替换并不限于版本号。您可以在任何地方使用它们,用于任何字符串值。例如,要针对不同的 MPI 实现构建

meta.yaml 文件内容如下:

package:
    name: compiled-code
    version: 1.0

requirements:
    build:
        - {{ mpi }}
    run:
        - {{ mpi }}

你可以像这样提供一个变体来构建这个配方(conda_build_config.yaml):

mpi:
    - openmpi  # version spec here is totally valid, and will apply in the recipe
    - mpich  # version spec here is totally valid, and will apply in the recipe

选择器在 conda_build_config.yaml 中是有效的,所以你可以为多个平台拥有一个 conda_build_config.yaml

mpi:
    - openmpi  # [osx]
    - mpich    # [linux]
    - msmpi    # [win]

不过,Jinja 在 conda_build_config.yaml 中是不允许的。它是提供给其他 Jinja 模板的信息来源,责任链必须在某个地方停止。

关于可重复性#

任何构建系统的一个关键部分是确保你可以在将来的某个时间点重复生成相同的输出。这对于调试错误通常是必不可少的。例如,如果一个包只包含二进制文件,了解创建这些二进制文件的源代码,从而了解可能存在的错误,将非常有用。

从 conda-build 2.0 开始,conda-build 已将它渲染的 meta.yaml 文件记录到每个它构建的包的 info/recipe 文件夹中。conda-build 3.0 在这方面没有区别,但记录的 meta.yaml 是构成该构建变体的变量的冻结集。

注意

包构建者可以通过在 meta.yaml 中的 build/include_recipe 键中禁用包含配方来禁用包含配方。如果配方从包中省略,则在没有源配方的情况下,该包不可重复。

特殊变体键#

有一些特殊键的行为有所不同,并且可以更嵌套。

  • zip_keys:字符串列表或字符串列表列表。字符串是变体中的键。它们将键组配对,以便特定键配对,而不是形成矩阵。这在 Windows 上将 vc 版本与 Python 版本配对时非常有用。更多信息请参见下面的 键配对 部分。

  • pin_run_as_build:应为字典。键是包名称。值是“固定表达式”——在 自定义兼容性 中有更详细的解释。这是 numpy x.x 规范的推广,因此你可以根据构建时使用的版本动态固定你的包。

  • extend_keys:指定应聚合而不是被后面的变体替换的键。这些将在下面的 扩展键 部分详细说明。

  • ignore_version:包名称列表,这些包的版本应从计算哈希时的 meta.yaml 的 requirements/build 中排除。在 避免不必要的构建 中有更详细的描述。

键配对#

有时特定版本需要与其他版本绑定。例如,在 Windows 上,我们通常遵循 Python.org 将 Visual Studio 编译器版本与 Python 版本关联的做法。Python 2.7 总是用 Visual Studio 2008(也称为 MSVC 9)编译的。我们不希望像下面这样的 conda_build_config.yaml 创建 Python/MSVC 版本的矩阵。

python:
  - 2.7
  - 3.5
vc:
  - 9
  - 14

相反,我们希望将 2.7 与 9 关联,将 3.5 与 14 关联。 conda_build_config.yaml 中的 zip_keys 键是实现此目的的方法。

python:
  - 2.7
  - 3.5
vc:
  - 9
  - 14
zip_keys:
  - python
  - vc

你也可以使用嵌套列表来实现多个 zip_keys 组。

zip_keys:
  -
    - python
    - vc
  -
    - numpy
    - blas

关于 zip_keys 的规则:

  1. 组中的每个列表都必须具有相同的长度。这是因为如果没有相等的长度,就没有办法将较短列表中的早期元素与较长列表中的后期元素关联起来。例如,以下无效,将引发错误。

    python:
      - 2.7
      - 3.5
    vc:
      - 9
    zip_keys:
      - python
      - vc
    
  2. zip_keys 必须是字符串列表或字符串列表列表。你不能将它们混合使用。例如,以下会出错。

    zip_keys:
      -
        - python
        - vc
      - numpy
      - blas
    

规则 #1 提出一个有趣的用例:如何将像 --python 这样的 CLI 标志与 zip_keys 结合起来?这样的 CLI 标志将更改变体,使其只有一个条目,但不会更改变体配置中的 vc 条目。最终我们将得到不匹配的列表长度和错误。要克服这个问题,你应该编写一个非常简单的 YAML 文件,其中包含所有相关的键。我们将其命名为 python27.yaml,以反映其意图。

python:
  - 2.7
vc:
  - 9

将此文件作为命令行参数提供。

conda build recipe -m python27.yaml

你也可以从 CLI 中指定 JSON 格式的变体,如 CONDA_* 变量和 conda-build 的命令行参数 部分所述。例如:

conda build recipe --variants "{'python': ['2.7', '3.5'], 'vc': ['9', '14']}"

避免不必要的构建#

为了避免构建不需要不同构建的包变体,你可以在你的变体中使用 ignore_version 键。然后将评估所有变体,但如果任何哈希值相同,则认为它们是重复的,并将进行重复数据删除。通过从构建依赖项中省略一些包,我们可以避免创建不必要的特定哈希值,并允许这种重复数据删除。

例如,让我们考虑一个包,它在运行和构建要求中都使用 NumPy,以及一个包含 2 个 NumPy 版本的变体。

variants = [{"numpy": ["1.10", "1.11"], "ignore_version": ["numpy"]}]

meta.yaml:

requirements:
    build:
        - numpy
    run:
        - numpy

在这里,变体表示我们将进行两次构建——每个 NumPy 版本一次。但是,由于这个配方没有固定 NumPy 的运行要求(因为它没有使用 NumPy 的 C API),因此没有必要针对 NumPy 1.10 和 1.11 都构建它。

这个配方的渲染形式,其中 conda-build 忽略了配方中 NumPy 的值,将只有一个构建,看起来像:

meta.yaml:

requirements:
    build:
        - numpy
    run:
        - numpy

ignore_version 默认情况下是一个空列表。实际执行的构建可能是在变体中的最后一个“numpy”列表元素上完成的,但这只是一个实现细节,你不应该依赖它。顺序被认为是不确定的行为,因为输出应该独立于输入版本。

警告

如果输出不独立于输入版本,请不要使用此键。

在运行要求中进行的任何固定都会影响哈希值,因此会对矩阵中的每个变体进行构建。任何有时用于其编译接口,有时仅用于其 Python 接口的包都可以从在后一种情况下谨慎使用 ignore_version 中获益。

注意

pin_run_as_build 有点类似于 ignore_version 的反面。如果它们发生冲突,pin_run_as_build 优先。

CONDA_* 变量和 conda-build 的命令行参数#

为了确保与 conda-build 的现有用户保持一致,像 CONDA_PY 这样的环境变量的行为始终如一,它们会覆盖在文件或 API 中设置的所有变体。

所有受尊重的环境变量的完整列表:

  • CONDA_PY

  • CONDA_NPY

  • CONDA_R

  • CONDA_PERL

  • CONDA_LUA

CLI 标志仍然可用。它们的存在是因为它们在一时性工作中很有用。

  • --python

  • --numpy

  • --R

  • --perl

  • --lua

除了这些传统选项之外,还有一个新标志用于指定变体:--variants。此标志接受一个 JSON 格式的文本字符串。例如:

conda build recipe --variants "{python: [2.7, 3.5], vc: [9, 14]}"

多个变体的聚合#

所有变体的矩阵首先从多个列表字典合并成一个列表字典,然后转换为列表字典(使用列表的笛卡尔积),其中每个值都是列表中潜在值的单个字符串。

例如,variants 的一般输入可能是这样的:

a = {"python": ["2.7", "3.5"], "numpy": ["1.10", "1.11"]}
# values can be strings or lists.  Strings are converted to one-element lists internally.
b = {"python": ["3.4", "3.5"], "numpy": "1.11"}

在这里,假设 ba 之后找到,因此优先于 a。合并这两个变体得到:

merged = {"python": ["3.4", "3.5"], "numpy": ["1.11"]}

bpython 的值已覆盖 a 的值。从这里,我们计算所有输入变量的笛卡尔积。最终结果是字典的集合,每个字典都有每个值的字符串。输出将类似于:

variants = [{"python": "3.4", "numpy": "1.11"}, {"python": "3.5", "numpy": "1.11"}]

conda-build 会在适当的时候遍历这些变体,例如在构建、输出包输出名称等情况下。

如果 numpy 有两个值而不是一个值,我们将得到 4 个输出变体:python 的 2 个变体,乘以 numpy 的 2 个变体。

variants = [
    {"python": "3.4", "numpy": "1.11"},
    {"python": "3.5", "numpy": "1.11"},
    {"python": "3.4", "numpy": "1.10"},
    {"python": "3.5", "numpy": "1.10"},
]

基于现有环境引导固定#

要建立你的初始变体,你可以指向一个现有的 conda 环境。conda-build 将检查该环境的内容,并固定到构成该环境的确切要求。

conda build --bootstrap name_of_env

你可以指定环境名称或环境的文件系统路径。请注意,指定环境名称确实意味着依赖于 conda 的环境查找。

扩展键#

这些不会被循环遍历来建立构建矩阵。相反,它们会从所有输入变体中聚合,每个派生变体都共享整个集合。这些在内部用于跟踪哪些要求应该被固定,例如,使用 pin_run_as_build 键。你可以通过为任何变体的 extend_keys 键传递值来添加你自己的扩展键。

例如,如果你想从多个 conda_build_config.yaml 文件中收集一些聚合特征,你可以这样做:

HOME/conda_build_config.yaml:

some_trait:
  - dog
extend_keys:
  - some_trait

recipe/conda_build_config.yaml:

some_trait:
  - pony
extend_keys:
  - some_trait

请注意,两个 conda_build_config.yaml 文件都需要将特征列为 extend_keys 条目。如果你只在一个文件中列出它,就会引发错误,以避免出现一个 conda_build_config.yaml 文件会向构建矩阵添加条目,而另一个不会的混淆。例如,这应该引发错误:

some_trait:
  - dog

recipe/conda_build_config.yaml:

some_trait:
  - pony
extend_keys:
  - some_trait

当我们将两个正确的 YAML 配置文件合并时,通常情况下,配方本地变体将覆盖用户范围变体,从而产生 {'some_trait': 'pony'}。但是,使用 extend_keys 条目,我们最终得到了我们一直想要的结果:一场狗和马的表演: {'some_trait': ['dog', 'pony'])}

同样,这主要是一个内部实现细节 - 除非你发现它的用途。在内部,它被用于聚合来自你任何 conda_build_config.yaml 文件的 pin_run_as_buildignore_version 条目。

自定义兼容性#

固定表达式#

固定表达式是用于指定要固定版本多少部分的语法。按照惯例,它们是包含 x 字符并用 . 分隔的字符串。要固定的版本部分数量就是用 . 分隔的元素数量。例如,"x.x" 固定主版本和次版本。 "x" 仅固定主版本。

在接受固定表达式的任何地方,你可以自定义下限和上限。

# produces pins like >=1.11.2,<1.12
variants = [{"numpy": "1.11", "pin_run_as_build": {"numpy": {"max_pin": "x.x"}}}]

请注意,最终固定可能比你最初的规范更具体。这里,规范是 1.11,但生成的固定可能是 1.11.2,即构建时使用的 NumPy 的确切版本。

# produces pins like >=1.11,<2
variants = [
    {"numpy": "1.11", "pin_run_as_build": {"numpy": {"min_pin": "x.x", "max_pin": "x"}}}
]

请注意,对于预发布版本,min_pin 将被忽略并替换为确切的输入版本,因为预发布版本永远不可能匹配 >=x.x(有关预发布版本匹配的详细信息,请参见 软件包匹配规范)。

在变体级别固定#

某些软件包,例如 boost,在运行时始终需要固定到构建时存在的版本。对于这些始终需要固定的情况,在变体级别固定是一个不错的选择。当满足以下条件时,Conda-build 会自动将运行时需求固定到构建环境中存在的版本。

  1. 依赖关系列在 requirements/build 部分中。它可以被固定,但不需要。

  2. 依赖关系以名称(没有固定)列在 requirements/run 部分中。

  3. 变体中的 pin_run_as_build 键的值是一个字典,包含一个与运行时需求中列出的依赖关系名称匹配的键。该值应该是一个包含最多 4 个键的字典:min_pinmax_pinlower_boundupper_bound。前两个是固定表达式。后两个是版本号,覆盖当前版本的检测。

这里显示了一个变体/配方的示例

conda_build_config.yaml:

boost: 1.63
pin_run_as_build:
    boost:
      max_pin: x.x

meta.yaml:

requirements:
    build:
        - boost
    run:
        - boost

这里的结果是,运行时 boost 依赖关系将被固定到 >=(current boost 1.63.x version),<1.64

有关 pin_run_as_build 函数的更多详细信息,请参见下面的 额外的 Jinja2 函数 部分。

请注意,有一些软件包你不应该使用 pin_run_as_build。那些并非始终需要固定的软件包应该在每个配方基础上进行固定(在下一节中描述)。NumPy 是这里一个有趣的例子。实际上,它并不能很好地作为变体级别固定的案例。因为你只需要针对使用 NumPy 的 C API 的配方进行这种固定,所以实际上最好不要使用 pin_run_as_build 固定 NumPy。固定它会不必要地过度约束你的需求,当你没有使用 NumPy 的 C API 时。相反,我们应该为每个使用 NumPy 的配方自定义它。另请参见上面的 避免不必要的构建 部分。

在配方级别固定#

在配方级别固定会覆盖在变体级别固定,因为 meta.yaml 中具有固定值的运行时依赖关系(即使是 Jinja 变量)也会被处理 pin_run_as_build 的逻辑忽略。我们预计,在配方级别的固定将用于当某个配方的固定相对于变体级别的某些标准固定而言异常严格(或宽松)时。

默认情况下,使用 pin_compatible('package_name') 函数,conda-build 会固定到你的当前版本,并且小于下一个主版本。对于不遵循语义版本控制理念的项目,你可能希望更严格地限制范围。为此,你可以向 pin_compatible 函数传递两个参数之一。

variants = [{"numpy": "1.11"}]

meta.yaml:

requirements:
    build:
        - numpy
    run:
        - {{ pin_compatible('numpy', max_pin='x.x') }}

这将产生一个 >=1.11.2,<1.12 的固定。

min_pinmax_pin 的语法是一个字符串固定表达式。每个表达式可以独立地传递。指定两个表达式的示例

variants = [{"numpy": "1.11"}]

meta.yaml:

requirements:
    build:
        - numpy
    run:
        - {{ pin_compatible('numpy', min_pin='x.x', max_pin='x.x') }}

这将产生一个 >=1.11,<1.12 的固定。

你也可以直接传递最小或最大版本。这些参数会取代 min_pinmax_pin 参数,因此它们是相互排斥的。

variants = [{"numpy": "1.11"}]

meta.yaml:

requirements:
    build:
        - numpy
    run:
        - {{ pin_compatible('numpy', lower_bound='1.10', upper_bound='3.0') }}

这将产生一个 >=1.10,<3.0 的固定。

追加到配方#

从 conda-build 3.0 开始,你可以在与你的 meta.yaml 文件相同的文件夹中添加一个名为 recipe_append.yaml 的文件。该文件被认为遵循与 meta.yaml 相同的规则,只是选择器和 Jinja2 模板不会被评估。选择器和 Jinja2 模板的评估可能会在未来的开发中添加。

recipe_append.yaml 中的任何内容都会添加到 meta.yaml 的内容中。列表值将被扩展,字符串值将被连接。该功能的提议用例是使用额外的需求调整/扩展中心配方,例如来自 conda-forge 的配方,同时将对配方文件的实际更改降到最低,以避免合并冲突和源代码分叉。

部分覆盖配方#

从 conda-build 3.0 开始,你可以在与你的 meta.yaml 文件相同的文件夹中添加一个名为 recipe_clobber.yaml 的文件。该文件被认为遵循与 meta.yaml 相同的规则,只是选择器和 Jinja2 模板不会被评估。选择器和 Jinja2 模板的评估可能会在未来的开发中添加。

recipe_clobber.yaml 中的任何内容都会替换 meta.yaml 的内容。例如,这对于替换源 URL 而不将配方的其余部分复制到分叉中很有用。

区分使用不同变体构建的软件包#

由于只支持少数几个东西,我们可以直接将它们添加到文件名中,例如 Python 的 py27,或 NumPy 的 np111。变体的目的是支持一般情况,在一般情况下,这不再是一个选项。相反,使用过的变体键和值使用 SHA1 算法进行哈希,该哈希是一个唯一的标识符。输入哈希的信息与软件包一起存储在 info/hash_input.json 中的文件中。只有当存在任何超出构建字符串(py、np 等)中已计入的变量的“使用”变量时,软件包才会具有哈希值。关键信息是,当二进制兼容性很重要时,哈希才会出现,而当不重要时,哈希就不会出现。

目前,只存储哈希的前 7 个字符。输出软件包名称将保留 pyXY 和 npXYY,但可能会添加 7 个字符的哈希值。你的软件包名称将如下所示

my-package-1.0-py27h3142afe_0.tar.bz2

从 conda-build 3.1.0 开始,该哈希方案已简化。如果任何依赖关系都满足以下所有条件,则会添加哈希值

  • 软件包是构建、主机或运行依赖关系中的显式依赖关系。

  • 软件包在 conda_build_config.yaml 中具有匹配的条目,该条目固定到特定版本,而不是固定到下限。

  • 该软件包不会被 ignore_version 忽略。

  • 软件包使用 {{ compiler() }} Jinja2 函数。

由于冲突只需要在软件包的一个版本内进行阻止,我们认为这将足够。如果你在这个有限的子空间中遇到哈希冲突,请在 conda-build 问题跟踪器 上提交问题。

有一个 CLI 工具可以很好地打印此 JSON 文件,以便于查看

conda inspect hash-inputs <package path>

这将产生以下输出

{'python-3.6.4-h6538335_1': {'files': [],
                            'recipe': {'c_compiler': 'vs2015',
                                        'cxx_compiler': 'vs2015'}}}

额外的 Jinja2 函数#

在处理这些 API 和 ABI 不兼容性时,两个特别常见的操作是指定这种兼容性的方法,以及明确表达要使用的编译器的方法。在评估 meta.yaml 模板时,可以使用三个新的 Jinja2 函数。

  • pin_compatible('package_name', min_pin='x.x.x.x.x.x', max_pin='x', lower_bound=None, upper_bound=None):用作运行和/或测试需求中的固定。接受软件包名称参数。查找构建环境中安装的命名软件包的兼容性,并为运行和/或测试需求编写兼容范围固定。默认情况下,它假设基于语义版本控制:package_name >=(current version),<(next major version)。传递 min_pinmax_pin 固定表达式。随着时间的推移,这将通过来自 ABI 实验室 的信息进行增强。

  • pin_subpackage('package_name', min_pin='x.x.x.x.x.x', max_pin='x', exact=False): 用于在运行和/或测试需求中作为固定值。接受包名称参数。用于在同一个配方中其他地方引用由父配方构建的子包的特定版本,作为依赖项。可以使用固定表达式,或精确的版本(包括构建字符串)。

  • compiler('language'): 最常用于构建需求。在必要时运行或测试。接受语言名称参数。这是一个简写,便于跨编译器使用。这个 Jinja2 函数将两个变量 {language}_compilertarget_platform 绑定在一起,并输出一个编译器包名称。例如,这可以用于在一个配方中编译针对 x86_64 和 arm 的输出,使用一个变体。

当没有在任何变体中指定编译器时,会使用默认的“本地”编译器。这些是在 conda-build 的 jinja_context.py 文件 中定义的。大多数情况下,用户不需要在他们的变体中提供编译器 - 只需将它们留空,conda-build 将使用适合您的系统的默认值。

引用子包#

Conda-build 2.1 引入了从单个配方构建多个输出包的功能。这在您有一个大型构建,同时输出许多东西,但这些东西实际上应该放在它们自己的包中时很有用。例如,构建 GCC 除了 GCC 本身,还会输出 GFortran、g++ 和 GCC、GFortran 和 g++ 的运行时库。为了尽可能保持清晰,这些都应该成为它们自己的包。不幸的是,如果存在单独的配方来重新打包来自更大整体包的不同部分,则很难保持同步。这就是变体发挥作用的地方。变体,更具体地说,pin_subpackage(name) 函数,提供了一种方法来引用子包,并控制子包版本关系与其他子包或父包之间的紧密程度。以下将输出 5 个 conda 包。

meta.yaml:

package:
  name: subpackage_demo
  version: 1.0

requirements:
  run:
    - {{ pin_subpackage('subpackage_1') }}
    - {{ pin_subpackage('subpackage_2', max_pin='x.x') }}
    - {{ pin_subpackage('subpackage_3', min_pin='x.x', max_pin='x.x') }}
    - {{ pin_subpackage('subpackage_4', exact=True) }}


outputs:
  - name: subpackage_1
    version: 1.0.0
  - name: subpackage_2
    version: 2.0.0
  - name: subpackage_3
    version: 3.0.0
  - name: subpackage_4
    version: 4.0.0

这里,父包将具有以下不同的运行时依赖项

  • subpackage_1 >=1.0.0,<2 (默认使用 min_pin='x.x.x.x.x.x', max_pin='x', 使用默认的 >= 当前版本下限固定到主要版本)

  • subpackage_2 >=2.0.0,<2.1 (更严格的上限)

  • subpackage_3 >=3.0,<3.1 (不太严格的下限,更严格的上限)

  • subpackage_4 4.0.0 h81241af (精确固定 - 版本加构建字符串)

编译器包#

在 macOS 和 Linux 上,我们可以并且确实会提供 GCC 包。随着变体的出现,这些将变得更加强大,因为您可以更明确地指定编译器的版本,并针对不同的版本进行构建,或者在编译器包的 activate.d 脚本中设置不同的标志。在 Windows 上,我们仍然使用系统上安装的编译器,而不是在包中提供实际的编译器。Windows 上的类似编译器包会运行任何编译器激活脚本并设置编译器标志,而不是实际安装任何东西。

随着时间的推移,conda-build 将要求所有包都以这种方式显式列出它们的编译器要求。这既是为了简化 conda-build,也是为了更好地跟踪与编译器相关的元数据 - 将其本地化为编译器包,即使这些包只做激活已经安装的编译器(例如,Visual Studio),也是如此。

还要注意 meta.yaml 中的 run_exports 键。这对于编译器配方来说很有用,因为它们可以根据编译器配方创建的子包版本来施加运行时约束。有关更多信息,请参阅 meta.yaml 文档的 导出运行时需求 部分。Anaconda 提供的编译器包广泛使用 run_exports 键。例如,包含 gcc_linux-cos5-x86_64 包作为构建时依赖项(直接或通过 {{ compilers('c') }} Jinja2 函数)的配方将自动添加兼容的 libgcc 运行时依赖项。

编译器版本#

通常,最新的编译器是最好的编译器,但在某些特殊情况下,您需要使用旧的编译器。

例如,NVIDIA 的 CUDA 库只支持经过严格测试的编译器。通常,最新的 GCC 编译器不支持与 CUDA 一起使用。如果您的配方需要使用 CUDA,您需要使用旧版本的 GCC。

与编译器相关联的特殊键。每个特殊键的键名是编译器键名加上 _version

例如,如果您的编译器键是 c_compiler,与之关联的版本键是 c_compiler_version。如果您有一个具有 GPU 支持的 Tensorflow 配方,请在 meta.yaml 旁边放置一个 conda_build_config.yaml 文件,其内容如下

c_compiler_version:    # [linux]
    - 5.4              # [linux]
cxx_compiler_version:  # [linux]
    - 5.4              # [linux]

指定选择器,以便此额外的版本信息不会应用于 Windows 和 macOS。这些平台具有完全不同的编译器,并且可能具有自己的版本(如果需要)。

没有必要指定 c_compilercxx_compiler,因为将使用默认值(Linux 上的 gcc)。有必要同时指定 ccxx 版本,即使它们相同,因为它们被独立对待。

通过将此文件放置在配方中,它将仅应用于此配方。所有其他配方将默认使用最新的编译器。

注意

您在此处指定的版本号必须作为包存在于您当前配置的通道中。

交叉编译#

编译器 Jinja2 函数被编写为支持交叉编译器。这取决于至少设置两个变体键:(language)_compilertarget_platform。目标平台将使用 _ 字符附加到 (language)_compiler 的值。这会导致像 g++_linux-aarch64 这样的包名称。我们建议使用以下约定来命名您的编译器包:<compiler name>_<target_platform>

在配方中使用交叉编译器将如下所示

variants = {
    "cxx_compiler": ["g++"],
    "target_platform": ["linux-cos5-x86_64", "linux-aarch64"],
}

以及一个 meta.yaml 文件

package:
    name: compiled-code
    version: 1.0

requirements:
    build:
        - {{ compiler('cxx') }}

这假设您已经创建了两个名为 g++_linux-cos5-x86_64g++_linux-aarch64 的编译器包 - conda-build 只为您提供了一种方法来遍历适当命名的交叉编译器工具链。

自一致的包生态系统#

编译器函数也是您支持非标准 Visual Studio 版本的方式,例如,使用 VS 2015 编译 Python 2.7 和 Python 2.7 的包。要实现这一点,您需要将 {{ compiler('<language>') }} 添加到构成系统的每个配方中。环境一致性通过依赖关系来维护 - 因此,使用版本化包作为运行时非常有用,而且一次只能安装一个版本。例如,最初由 Conda-Forge 创建的 vc 包是一个版本化包(一次只能安装一个版本),它会安装正确的运行时包。当编译器包强制执行这种运行时依赖项时,结果生态系统将是自一致的。

根据这些指南,考虑使用以下变体的一组配方

variants = {"cxx_compiler": ["vs2015"]}

配方包括一个编译器 meta.yaml,如下所示

package:
    name: vs2015
    version: 14.0
build:
    run_exports:
        - vc 14

它们还包括一些使用编译器的 meta.yaml 内容,如下所示

package:
    name: compiled-code
    version: 1.0

requirements:
    build:
        # these are the same (and thus redundant) on windows, but different elsewhere
        - {{ compiler('c') }}
        - {{ compiler('cxx') }}

这些配方将创建一个包系统,这些包都使用 VS 2015 编译器构建,并且 vc 包的版本与版本 14 相匹配,而不是与 Python 版本相关的任何默认版本。