构建变体#

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

长期以来,对此的支持有限。在构建和运行需求中都包含 Python 会导致软件包的 Python 版本被锁定为构建时使用的 Python 版本,并在文件名中添加相应的后缀,例如“py27”。在 Conda-build PR 573 合并后,NumPy 也存在类似的支持,在配方中添加了 x.x pin。在 conda-build 3.0 版本之前,也有许多关于通用支持的长期提案 (Conda-build issue 1142)。

从 conda-build 3.0 开始,添加了一种新的配置方案,称为“变体”。从概念上讲,这会将锁定值与配方分离,并用 Jinja2 模板变量替换它们。它增加了对“兼容”锁定的概念的支持,以便与 ABI 兼容性数据库(例如 ABI Laboratory)集成。请注意,“兼容”锁定的概念目前仍处于高度开发中。

变体输入最终是一个字典。这些字典大多非常扁平。键直接在 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. 提供二进制接口的共享库。此库的所有用途都使用二进制接口。将相同的 pin 应用于您的所有构建很方便。示例: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 pin 主要和次要版本。更多信息见 锁定表达式

  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
    

    此示例演示了一个特殊功能:在不需要 pin 时减少构建。由于上面的示例配方仅需要 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/etc。

    动态锁定是棘手的部分。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 软件包的精确 pin。

创建 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']}"

避免不必要的构建#

为了避免构建不需要 pin 的软件包变体,您可以使用变体中的 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"}

在这里,假设在 a 之后找到 b,因此它优先于 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 有两个值而不是一个值,我们将最终得到四个输出变体: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"},
]

基于现有环境引导 pin#

要建立您的初始变体,您可以指向现有的 conda 环境。Conda-build 将检查该环境的内容,并 pin 到构成该环境的确切需求。

conda build --bootstrap name_of_env

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

扩展键#

这些键不会被循环访问以建立构建矩阵。相反,它们是从所有输入变体中聚合而来的,并且每个派生的变体都共享整个集合。这些键在内部用于跟踪哪些需求应被 pin,例如,使用 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"}}}]

请注意,最终 pin 可能比您的初始规范更具体。在这里,规范是 1.11,但是生成的 pin 可能是 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"}}}
]

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

在变体级别进行锁定#

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

  1. 依赖项在 requirements/build 部分中列出。它可以被锁定,但不是必须的。

  2. 依赖项在 requirements/run 部分中按名称列出(未锁定)。

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

此处显示了一个示例变体/配方

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 的版本限制。

附加到 recipes#

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

recipe_append.yaml 中的任何内容都将添加到 meta.yaml 的内容中。列表值将被扩展,字符串值将被连接。此文件的建议用例是调整/扩展中央 recipes,例如来自 conda-forge 的 recipes,并添加额外的要求,同时最大限度地减少对 recipe 文件的实际更改,以避免合并冲突和源代码发散。

部分覆盖 recipes#

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

recipe_clobber.yaml 中的任何内容都将替换 meta.yaml 的内容。例如,这对于在不将 recipe 的其余部分复制到分支的情况下替换源 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 版本开始,此哈希方案已得到简化。如果以下所有条件对于任何依赖项都为真,则将添加哈希值

  • 软件包是 build、host 或 run deps 中的显式依赖项。

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

  • 该软件包未被 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):用作 run 和/或 test requirements 中的 pin。接受软件包名称参数。查找构建环境中安装的命名软件包的兼容性,并为 run 和/或 test requirements 写入兼容范围 pin。默认为基于 semver 的假设:package_name >=(current version),<(next major version)。传递 min_pinmax_pin Pinning expressions 。随着时间的推移,这将通过来自 ABI Laboratory 的信息得到增强。

  • pin_subpackage('package_name', min_pin='x.x.x.x.x.x', max_pin='x', exact=False):用作 run 和/或 test requirements 中的 pin。接受软件包名称参数。用于引用父 recipe 构建的子软件包的特定版本,作为该 recipe 中其他位置的依赖项。可以使用 pinning expressions,也可以使用 exact(包括构建字符串)。

  • compiler('language'):最常用于 build requirements 中。根据需要用于 Run 或 test。接受语言名称参数。这是简化跨编译器用法的简写。此 Jinja2 函数将 2 个变体变量 {language}_compilertarget_platform 结合在一起,并输出单个编译器软件包名称。例如,这可以用于在一个 recipe 中编译针对 x86_64 和 arm 的输出,并使用变体。

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

引用子软件包#

Conda-build 2.1 引入了从单个 recipe 构建多个输出软件包的功能。这在您有一个大型构建一次输出很多东西的情况下非常有用,但这些东西实际上属于它们自己的软件包。例如,构建 GCC 不仅输出 GCC,还输出 GFortran、g++ 以及 GCC、GFortran 和 g++ 的运行时库。为了尽可能简洁,这些都应该是它们自己的软件包。不幸的是,如果存在单独的 recipes 来重新打包来自较大、完整软件包的不同部分,则很难保持它们同步。这就是变体的用武之地。变体,更具体地说是 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.xmax_pin='x',pin 到主要版本,默认 >= 当前版本下限)

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

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

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

编译器软件包#

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

随着时间的推移,conda-build 将要求所有软件包都以这种方式显式列出其编译器要求。这既是为了简化 conda-build,也是为了改进与编译器关联的元数据的跟踪 - 将其本地化到编译器软件包,即使这些软件包仅仅是激活已安装的编译器(例如 Visual Studio)。

另请注意 meta.yaml 中的 run_exports 键。这对于编译器 recipes 基于编译器 recipe 创建的子软件包版本施加运行时约束非常有用。有关更多信息,请参阅 meta.yaml 文档的 导出运行时需求 部分。Anaconda 提供的编译器软件包广泛使用 run_exports 键。例如,将 gcc_linux-cos5-x86_64 软件包作为构建时依赖项(直接或通过 {{ compilers('c') }} Jinja2 函数)包含在内的 recipes 将自动添加兼容的 libgcc 运行时依赖项。

编译器版本#

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

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

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

例如,如果您的编译器键是 c_compiler,则与其关联的版本键是 c_compiler_version。如果您有用于支持 GPU 的 Tensorflow 的 recipe,请在 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 版本,因为它们是独立处理的。

通过将此文件放在 recipe 中,它将仅应用于此 recipe。所有其他 recipes 将默认为最新编译器。

注意

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

交叉编译#

编译器 Jinja2 函数旨在支持交叉编译器。这取决于至少设置 2 个变体键:(language)_compilertarget_platform。目标平台附加到 (language)_compiler 的值,并带有 _ 字符。这会导致软件包名称类似于 g++_linux-aarch64。我们建议将您的编译器软件包命名为以下约定:<compiler name>_<target_platform>

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

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') }}

这假定您已创建了 2 个名为 g++_linux-cos5-x86_64g++_linux-aarch64 的编译器软件包 - conda-build 为您提供的只是循环遍历适当命名的交叉编译器工具链的方法。

自洽的软件包生态系统#

编译器函数也是您如何支持非标准 Visual Studio 版本的方式,例如使用 VS 2015 编译 Python 2.7 和 Python 2.7 的软件包。要实现此目的,您需要将 {{ compiler('<language>') }} 添加到构成系统的每个 recipe 中。环境一致性通过依赖项来维护 - 因此,最好让运行时成为版本化的软件包,一次只能安装一个版本。例如,vc 软件包最初由 Conda-Forge 创建,是一个版本化的软件包(一次只能安装一个版本),它安装正确的运行时软件包。当编译器软件包施加这样的运行时依赖项时,生成的生态系统是自洽的。

鉴于这些准则,考虑一个使用如下变体的 recipes 系统

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

recipes 包括一个编译器 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') }}

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