跳转到主要内容

CEP 13 - 新的配方格式(第一部分)

标题新的配方格式(第一部分)
状态已接受
作者Wolf Vollprecht <wolf@prefix.dev>
创建日期2023年5月23日
更新日期2023年10月20日
讨论https://github.com/conda-incubator/ceps/pull/54
实现https://github.com/prefix-dev/rattler-build

摘要

我们提出了一个新的配方格式,它深受 conda-build 的启发。主要的改变是纯 YAML 格式,不包含任意的 Jinja 或带有语义含义的注释。

动机

conda-build 格式多年来变得相当复杂。不幸的是,它从未被正式“规范化”,并且随着时间的推移,它增长了一些特性,使得它难以作为直接的 YAML 进行解析。

本 CEP 尝试引入 conda build 格式的一个子集,该子集允许快速解析和构建配方。

历史

关于新的配方规范可能或应该是什么样子的讨论已经开始。可以在这里找到此讨论的片段:https://github.com/mamba-org/conda-specs/blob/master/proposed_specs/recipe.md 新规范的原因是

  • 使其更易于解析(“纯 yaml”)。conda-build 混合使用注释和 jinja 来实现很大的灵活性,但是用计算机解析配方很困难
  • 解决围绕多个输出的一些不一致性(build 与 build/script 等等)
  • 消除对递归解析和求解的任何需求
  • 通过确定性格式满足自动化和依赖树分析的需求

与 conda-build 的主要区别

  • 没有完整的 Jinja2 支持:没有块支持 {% set ... 支持,只有字符串插值。变量可以在顶层“context”中设置,这是有效的 YAML(所有新功能都应该是 YAML 规范的原生功能)
  • Jinja 变量语法已更改为以 ${{ 开头,以便它成为有效的 YAML,例如 - ${{ version }}
  • 选择器使用带有 if / then / else 的 YAML 字典(相对于 conda-build 中的注释),并且仅允许在列表(dependencies、scripts 等)中使用。语法如下所示
    - if: win
    then: this
    else: that # optional
  • 对于内联值,可以使用 Jinja 三元运算符,例如 number: ${{ 0 if linux else 100 }}

选择器

新规范中的选择器仅允许在列表中使用,并采用显式的 if / then / else 语法。

例如,以下 script 部分

script:
- if: unix
then: |
# this is the unix script
- if: win
then: |
@rem a script for batch

同样可以用 else 来表达

script:
- if: unix
then: |
# this is the unix script
else: |
@rem a script for batch

这是一个有效的 YAML 字典。选择器 if 语句是简单的布尔表达式,并遵循 Python 语法。以下选择器都是有效的

win and arm64
(osx or linux) and aarch64
something == "test"

如果选择器语句的值是一个列表,它会扩展“外部”列表。例如

build:
- ${{ compiler('cxx') }}
- if: unix
then:
- make
- cmake
- pkg-config

对于 unix == true,求值为一个包含元素 [${{ compiler('cxx') }}, make, cmake, pkg-config] 的列表。

预处理选择器

您可以向任何项目添加选择器,并且选择器在预处理阶段进行评估。如果选择器评估为 true,则该项目将被展平到父元素中。如果选择器评估为 false,则该项目将被删除。

source:
- if: not win
then:
# note that we omit the `-`, both is valid
url: http://path/to/unix/source
sha256: 01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b
else:
- url: http://path/to/windows/source
sha256: 06f961b802bc46ee168555f066d28f4f0e9afdf3f88174c1ee6f9de004fc30a0

由于选择器是有效的 Jinja 表达式,因此可以实现复杂的逻辑

source:
- if: win
then:
url: http://path/to/windows/source
- if: (unix and cmp(python, "2"))
then:
url: http://path/to/python2/unix/source
- if: unix and cmp(python, "3")
then:
url: http://path/to/python3/unix/source

列表会自动“向上合并”,因此可以将多个项目分组在单个选择器下

test:
commands:
- if: unix
then:
- test -d ${PREFIX}/include/xtensor
- test -f ${PREFIX}/include/xtensor/xarray.hpp
- test -f ${PREFIX}/lib/cmake/xtensor/xtensorConfig.cmake
- test -f ${PREFIX}/lib/cmake/xtensor/xtensorConfigVersion.cmake
- if: win
then:
- if not exist %LIBRARY_PREFIX%\include\xtensor\xarray.hpp (exit 1)
- if not exist %LIBRARY_PREFIX%\lib\cmake\xtensor\xtensorConfig.cmake (exit 1)
- if not exist %LIBRARY_PREFIX%\lib\cmake\xtensor\xtensorConfigVersion.cmake (exit 1)

# On unix this is rendered to:
test:
commands:
- test -d ${PREFIX}/include/xtensor
- test -f ${PREFIX}/include/xtensor/xarray.hpp
- test -f ${PREFIX}/lib/cmake/xtensor/xtensorConfig.cmake
- test -f ${PREFIX}/lib/cmake/xtensor/xtensorConfigVersion.cmake

使用 Jinja 模板

该规范支持在 recipe.yaml 文件中使用简单的 Jinja 模板。

您可以在 context YAML 部分设置 Jinja 变量

context:
name: "test"
version: "5.1.2"
major_version: ${{ version.split('.')[0] }}

稍后在您的 recipe.yaml 中,您可以使用这些值通过 Jinja 进行字符串插值。例如

source:
url: https://github.com/mamba-org/${{ name }}/v${{ version }}.tar.gz

Jinja 内置支持一些常见的字符串操作。

在新规范中,完全禁止复杂的 Jinja,因为我们尝试生成始终有效的 YAML。因此,您不应使用任何会产生无效 YAML 的 {% if ... %} 或类似的 Jinja 结构。我们也不使用标准的 Jinja 分隔符 ({{ .. }}),因为 YAML 解析器会将其误认为是字典。我们效仿 Github Actions 和其他工具,改用 ${{ ... }}

package:
name: {{ name }} # WRONG: invalid yaml
name: ${{ name }} # correct

Jinja 函数像往常一样工作。例如,compiler Jinja 函数将如下所示

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

缺点

由于我们特意限制了配方中允许使用的“Jinja”数量,因此会存在一些缺点。

例如,禁止使用 {% for ... %} 循环。在使用 Github 搜索搜索 conda-forge 配方后,我们发现 for 循环主要用于测试中。

我们认为,for 循环是一个很好的助手,但对于许多任务来说不是必需的:例如,相同的功能可以在测试脚本中实现。与此同时,我们也计划正式化一个更强大的测试工具(在 boa 中原型化)。

这可以用来代替 for 循环来跨平台检查共享库或头文件的存在性(而不是像这里这里那样依赖 Jinja 模板)。

for 循环的其他用途应该相对容易重构,例如这里

但是,由于新的配方格式是“纯 YAML”,因此使用脚本创建和预处理这些文件非常容易,甚至可以使用 Python 或任何其他脚本语言生成它们。这意味着,目前使用 Jinja 完成的许多功能将来可以通过简单的预处理步骤来完成。

另一种选择是在测试脚本文本块内允许“完整”的 Jinja(只要它不改变 YAML 的结构)。

示例

xtensor

原始配方在此处找到。found here

context:
name: xtensor
version: 0.24.6
sha256: f87259b51aabafdd1183947747edfff4cff75d55375334f2e81cee6dc68ef655

package:
name: ${{ name|lower }}
version: ${{ version }}

source:
fn: ${{ name }}-${{ version }}.tar.gz
url: https://github.com/xtensor-stack/xtensor/archive/${{ version }}.tar.gz
sha256: ${{ sha256 }}

build:
number: 0
# note: in the new recipe format, `skip` is a list of conditional expressions
# but for the "YAML format" discussion we pretend that we still use the
# `skip: bool` syntax
skip: ${{ true if (win and vc14) }}

requirements:
build:
- ${{ compiler('cxx') }}
- cmake
- if: unix
then: make
host:
- xtl >=0.7,<0.8
run:
- xtl >=0.7,<0.8
run_constrained:
- xsimd >=8.0.3,<10

test:
commands:
- if: unix
then:
- test -d ${PREFIX}/include/xtensor
- test -f ${PREFIX}/include/xtensor/xarray.hpp
- test -f ${PREFIX}/share/cmake/xtensor/xtensorConfig.cmake
- test -f ${PREFIX}/share/cmake/xtensor/xtensorConfigVersion.cmake
- if: win
then:
- if not exist %LIBRARY_PREFIX%\include\xtensor\xarray.hpp (exit 1)
- if not exist %LIBRARY_PREFIX%\share\cmake\xtensor\xtensorConfig.cmake (exit 1)
- if not exist %LIBRARY_PREFIX%\share\cmake\xtensor\xtensorConfigVersion.cmake (exit 1)

about:
home: https://github.com/xtensor-stack/xtensor
license: BSD-3-Clause
license_family: BSD
license_file: LICENSE
summary: The C++ tensor algebra library
description: Multi dimensional arrays with broadcasting and lazy computing
doc_url: https://xtensor.readthedocs.io
dev_url: https://github.com/xtensor-stack/xtensor

extra:
recipe-maintainers:
- some-maintainer