跳到主要内容

conda-forge 上的 macOS ARM 构建

·8 分钟阅读
Isuru Fernando
conda-forge/core 成员

一个新的平台 osx-arm64 已被添加到 conda-forge 的构建矩阵中。osx-arm64 软件包被构建为在即将到来的 macOS arm64 处理器(以 Apple Silicon 名义销售)上运行。此平台的安装程序可以在这里找到。

这将安装一个包含 python 和 conda 的 conda 环境。安装的 conda 将能够安装像 numpy, scipy 这样的软件包。目前,为该平台预构建的 10000 个软件包中约有 100 个。

所有这些软件包都是在 conda-forge 当前的 macOS x86_64 基础设施上构建的。为了做到这一点,我们对基础设施进行了大量更改,包括 conda, conda-build, conda-smithy, constructor, conda-forge-ci-setup,以方便交叉编译,交叉编译是在 构建 平台(在我们的例子中为 osx-64linux-64)上完成编译,编译将在 宿主 平台(在我们的例子中为 osx-arm64)上运行的软件包的过程。

osx-arm64 是第一个完全使用 conda-build 的交叉编译工具引导的 conda 平台。以前,在添加新平台时,conda-build 是在新平台上使用现有的 python 和 pip 环境构建的。通过交叉编译,当编译器和 sysroot 在不同的平台上设置好后,现有的 conda-build 安装(在本例中为 osx-64linux-64)将能够立即开始构建软件包。

osx-arm64 的交叉编译构建

为了为 osx-arm64 交叉编译软件包,我们需要编译器。因此,我们首先构建了 clang=11.0.0.rc1,它支持以 osx-arm64 为目标。我们还构建了 compiler-rt=11.0.0.rc1 作为同时支持 osx-64osx-arm64 的通用构建。

链接器、归档器、otoolinstall_name_tool 是使用 Thomas Pöchtrager 的 cctools-port 项目构建的。

我们遇到的一个问题是 macOS 11.0 Big Sur Beta 7 要求所有可执行文件和共享库都必须进行临时签名,即在没有验证签名的情况下进行签名。在 cctools-port 开发人员的建议下,我们为 cctools-port 添加了使用 ldid 签名这些可执行文件的支持,ldid 可以在 Linux 和 macOS 上使用进行签名。

使用这些,我们构建的第一个交叉编译软件包是 libcxx,以方便 C++ 构建。对于 osx-arm64 sysroot,我们使用了 Azure pipelines 和 Travis-CI 上已经安装的 MacOSX11.0.sdk。由于许可问题,我们无法分发它,但即使在 Linux 上也可以从 Apple 开发者网站下载。

有了 clang,我们就有了 C/C++ 编译器,但缺少 Fortran 编译器。我们使用了 用于 darwin-arm64 的 GCC 分支。首先,构建了一个交叉编译器(build == host != target)。使用该编译器,我们构建了一个 交叉原生 编译器(build != host == target),它为我们提供了像 libgfortran.dylib 这样的共享库。

我们还在 conda 中的 rust 软件包中添加了对交叉编译 rust 程序的支持,在 Linux 上安装 rust_osx-64 将为您提供一个编译器,该编译器将为 osx-64 构建软件包。

由于我们以前没有做过交叉编译,许多软件包需要更新。大多数都是微不足道的更改,我们稍后将其自动化。这些更改包括获取更新的 config.sub 以识别新的 autotools 平台 arm64-apple-darwin20.0.0,使用环境变量 CMAKE_ARGS 向 CMake 添加选项以正确设置工具链,以及更新配方以使用 cmake ${CMAKE_ARGS} ..。在构建时运行测试也被禁用,通过使用环境变量 CONDA_BUILD_CROSS_COMPILATION 保护像 make checkmake testctest 这样的命令。

交叉编译 python 扩展非常棘手,因为 distutils 实际上并没有设置为这样做。感谢 crossenv 项目,这在一些怪癖下得到了非官方的支持。使用 crossenv,我们可以在构建机器(在本例中为 osx-64linux-64)上运行一个 python,其行为就像它在 osx-arm64 上一样。crossenv monkey-patch 了一些函数,如 os.uname,并设置了像 _PYTHON_SYSCONFIG_DATA 这样的值,以使在 osx-64linux-64 上运行的 python 表现得像 osx-arm64。一个问题是,monkey-patching sys.platform 不起作用,因此,如果 python 软件包在其 setup.py 中使用 sys.platform 来区分操作系统,如果您从 linux-64 进行交叉编译,这将导致意想不到的后果。因此,当为 osx-arm64 进行交叉编译时,我们必须使用 osx-64 作为我们的 构建 系统。请注意,使用 sysconfig.get_platform() 的软件包将获得正确的平台。

为了为 conda 创建安装程序,我们需要一个独立的 conda 可执行文件来引导 conda 环境。对于其他平台,我们依赖于 conda-standalone,这是一个使用 pyinstaller 创建的独立 conda 可执行文件。由于 pyinstaller 不支持交叉编译,我们决定使用 micromamba 作为引导程序,并向 micromamba 添加了功能,使其可以作为引导程序运行。

如何向 feedstock 添加 osx-arm64 构建

以下所有更改都将由机器人完成,机器人将向其发送 PR 的软件包由 conda-forge-pinning 及其依赖项中的软件包列表确定。如果您想添加支持,请发送 PR 将 feedstock 名称添加到上面的列表中。在该 PR 合并后,您可以在 conda-forge 状态页面上监控状态,如果某个 PR 停滞不前,您可以向 feedstock 发送 PR 以修复它。

以下说明适用于您想要手动添加支持的情况。

将以下内容添加到 conda-forge.yml(在 Linux 或 OSX 上),

build_platform:
osx_arm64: osx_64
test: native_and_emulated

您可以使用以下命令重新渲染:

conda smithy rerender

对于 python 软件包,根据需要将以下一个或多个添加到 recipe/meta.yaml,请注意,如果 numpycython 和/或 pybind11 也用于 host: 中,您必须只添加它们,

requirements:
build:
- python # [build_platform != target_platform]
- cross-python_{{ target_platform }} # [build_platform != target_platform]
- cython # [build_platform != target_platform]
- numpy # [build_platform != target_platform]
- pybind11 # [build_platform != target_platform]

对于 autotools 软件包,将以下内容添加到 recipe/meta.yaml

requirements:
build:
- gnuconfig # [unix]

并添加到 recipe/build.sh

# Get an updated config.sub and config.guess
cp $BUILD_PREFIX/share/gnuconfig/config.* .

对于 cmake 软件包,将以下内容添加到 recipe/build.sh

cmake ${CMAKE_ARGS} ..

对于 meson 软件包,将以下内容添加到 recipe/build.sh

meson ${MESON_ARGS} builddir/
注意

Conda 在交叉编译时自动创建一个 交叉构建定义文件,并将必要的参数添加到 ${MESON_ARGS} 以将 meson 指向该文件。${MESON_ARGS} 仅在交叉编译时定义,而不是在正常构建时定义。

对于 rust 软件包,将以下内容添加到 recipe/meta.yaml

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

如果 recipe/build.sh 中有一行像 make check 这样的行在交叉编译时无法运行,请执行以下操作,

if [[ "$CONDA_BUILD_CROSS_COMPILATION" != "1" ]]; then
make check
fi

在进行这些更改后,可能需要再次重新渲染。

一些有用的 jinja 变量,

  1. build_platform - BUILD_PREFIX 的 conda 子目录。例如:linux-64
  2. target_platform - PREFIX 的 conda 子目录。例如:osx-arm64

一些有用的环境变量,

  1. build_platform
  2. target_platform
  3. CONDA_BUILD_CROSS_COMPILATION - 如果是交叉编译则为 1
  4. CMAKE_ARGS - 传递给 cmake 的参数
  5. CC_FOR_BUILD - 构建平台的 C 编译器
  6. CXX_FOR_BUILD - 构建平台的 C++ 编译器
  7. HOST - 传递给 autoconf 的宿主三元组。例如:arm64-apple-darwin20.0.0
  8. BUILD - 传递给 autoconf 的构建三元组。例如:x86_64-conda-linux-gnu

conda-forge.yml 中一些有用的配置选项

  1. build_platform - 将 build 子目录映射到 host 子目录的字典。例如

    build_platform:
    osx_arm64: osx_64
    linux_ppc64le: linux_64
    linux_aarch64: linux_64
  2. test_on_native_only - 一个布尔值,用于关闭交叉编译时的测试。如果测试不需要模拟(例如:检查文件是否存在),则 test_on_native_only: false 将在交叉编译时也运行测试。

本地构建

对于本地构建,请在 $HOME/conda_build_config.yaml 中添加以下内容。

SDKROOT:
- /path/to/MacOSX11.0.sdk

之后,在 feedstock 的根目录中的 .ci_support 文件夹中查找您想要运行的配置。例如:.ci_support/osx_arm64_.yaml。然后运行,

conda build recipe -m .ci_support/osx_arm64_.yaml -c conda-forge -c conda-forge/label/rust_dev

这应该为 osx-arm64 启动一个新的构建。

测试软件包

为了测试旨在在未来的 Apple Silicon 硬件上运行的软件包,Apple 提供了一台名为 Developer Transition Kit (DTK) 的机器。Jonathan Helmus 和 Eli Rykoff 帮助在 DTK 上测试了这些软件包。感谢 Eli Rykoff,我们现在正在为这些软件包运行每日 cron 作业,这导致在我们的交叉编译基础设施中以及我们的配方中发现了几个错误。

要测试交叉编译的配方,请将构建的 conda 软件包传输到 host 并运行,

conda build --test /path/to/package -c conda-forge

如果没有许多人的帮助,这项工作是不可能完成的,这些人包括编译器基础设施的上游维护者(包括 conda、conda-build、cctools、tapi、cctools-port、ldid、llvm、clang、compiler-rt、openmp、libcxx、crossenv、rust、gcc-darwin-arm64)、conda-forge/help-osx-arm64 团队,包括 Matt Becker、Eli Rykoff 和 Uwe Korn,他们发送了 PR 来修复配方、conda-forge/bot 团队以及所有审查和修复 PR 的 100 个 feedstock 的 conda-forge 维护者。

Isuru Fernando