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