我们曾多次被要求为 ConanCenter 提供一些 macOS ARM 二进制文件,现在它们终于来了!本文介绍了以下几个方面

  • 关于 Apple M1 的一般信息
  • 如何在 ConanCenter 中创建 Apple M1 二进制文件
  • 如何调整现有配方以支持交叉编译到 Apple M1

关于 Apple M1

Apple M1 是苹果公司开发的一种新的基于 ARM 的 SoC,基于 ARMv8 架构。苹果已经开始从 Intel x86-64 向 ARM 过渡,新的台式电脑(MacBook Air、MacBook Pro、Mac Mini 和 iMac)已经配备了 M1 处理器。我们预计随着过渡的进行,移植到 ARM 的软件的普及度和需求将会显著增长。为了开始为 M1 创建您的软件包,您不需要任何特殊的东西。Conan 已经提供了针对此新架构的构建助手和工具。您只需要为创建命令提供正确的输入,基本上是一个带有以下设置的主机配置文件

$ conan profile show m1

[settings]
os=Macos
arch=armv8
...

Conan 将为此新架构生成二进制文件,您可以安全地将它们上传到您自己的 Artifactory,由于新的设置值,Conan 将计算不同的 软件包 ID

当然,您的构建机器需要提供此构建所需的工具,但任何更新的 macOS 都可以完成此工作。

关于架构命名的困惑

关于 arm64/aarch64/armv8 架构命名的困惑在开发社区中已经 众所周知。让我们尝试澄清一下

  • ARMv8 - 指的是 ARM 架构的新版本。它包括两种执行状态,AArch32AArch64,以及每个状态对应的指令集。
  • ARMv8-A - 特指 ARM 架构的 *A*(应用程序)配置文件。其他配置文件为 *R*(实时)和 *M*(微控制器)。或者,分别为 ARMv8-RARMv8-M
  • AArch64 - ARMv8 架构的 64 位执行状态。
  • A64 - 指的是 AArch64 执行状态的指令集。此名称很少在 ARM 文档之外使用。
  • ARM64 - 一个非官方(例如,在官方 ARM 文档中未使用)但非常流行的名称,与 AArch64 同义。Apple 软件(例如 Xcode 和其他开发工具)使用此命名。此外,许多 Linux 发行版也使用它。

在 Conan 中,我们将这些二进制文件称为 armv8(表示 Aarch64 指令集的二进制文件),因此使用者必须在他们的配置文件或命令行中指定 arch=armv8

我们如何使用交叉编译在 ConanCenter 中创建 Apple M1 二进制文件

2021年5月24日 开始,我们就在 ConanCenter 中构建 Apple M1 二进制文件。我们希望帮助希望将其应用程序定位到此新架构并目前正在使用 Conan 管理其依赖项的开发者社区。

如前所述,使用 Conan 为 Apple M1 生成二进制文件非常简单,我们只需要使用 macOS/ARMv8 的配置文件并构建我们的软件包即可。

调整 ConanCenter 中现有的配方以进行交叉编译

一些软件包声明了它们的构建依赖项,并且需要在构建库时执行它们。将 CI 系统连接到 M1 机器会更容易,但我们决定使用我们已经在使用的现有 macOS 机器来交叉编译这些软件包。

我们知道这会更难,因为这是 ConanCenter 第一个生成交叉编译二进制文件的配置,但同时,付出努力也会得到回报,因为配方的质量将会提高,将来添加新的交叉编译场景也会更容易(iOS、Android、Emscripten、Raspberry Pi 等)。

以下是一些我们需要对配方进行的最常见的调整

请注意,在添加 M1 支持时 ConanCenter 中使用的 Conan 版本为 1.38,并且新的 构建助手 还不够成熟,无法在生产配方中使用。一些以下修复程序在新的集成中将不再适用,我们确实希望能够在它们可用后简化配方。

这是我们第一次在 ConanCenter 中支持实际的交叉编译,因此许多配方或库可能没有为此做好准备。如果您需要,可以暂时跳过 M1 支持

from conans.errors import ConanInvalidConfiguration

...

def validate(self):
    if self.settings.os == "Macos" and self.settings.arch == "armv8":
        raise ConanInvalidConfiguration("sorry, M1 builds are not currently supported, give up!")

当然,我们鼓励贡献者为其他平台提供支持,但这可能首先需要上游的支持。

在 test_package 中检查交叉编译

由于我们目前在 CI 上使用的 Intel Mac 机器将无法执行 ARM 代码,因此现在需要对 test_package 进行以下额外检查

    def test(self):
        if not tools.cross_building(self):
            bin_path = os.path.join("bin", "test_package")
            self.run(bin_path, run_environment=True)

2 个配置文件

这也是我们第一次在 ConanCenter 中使用 两个配置文件模型。长期计划是将所有配置迁移到使用两个配置文件,因为这将成为 Conan 2.0 中的默认模式。

要使用 Conan 交叉编译 M1 二进制文件,可以使用以下命令行

$ conan install . --profile:build default --profile:host m1

或简写形式

$ conan install . -pr:b default -pr:h m1

使用极其简单的 m1 配置文件

$ conan profile show m1

include(default)
[settings]
arch=armv8
os=Macos
compiler=apple-clang
compiler.version=12.0
compiler.libcxx=libc++
build_type=Release
[options]
[build_requires]
[env]

由于切换到两个配置文件方法,配方存在一些重要的副作用

不应使用 os_build 和 arch_build

对于使用两个配置文件方法的配方,不再建议使用 os_buildarch_build 设置。如果需要检查 构建上下文osarch,可以使用以下代码

@property
def _settings_build(self):
    return self.settings_build if hasattr(self, "settings_build") else self.settings

if self._settings_build.os == "Windows":
	# build context: os is Windows

我们强烈建议作者从他们的配方中删除 os_buildarch_build。我们还强烈建议所有使用者在尽可能的情况下将其交叉编译场景迁移到两个配置文件方法。原因是构建上下文允许使用构建需求获得更好的兼容性,避免为安装程序和工具创建单独的软件包。

不应使用 tools.OSInfo、platform.system()、os.name

另一件重要的事情是 ConanCenter CI 在两个阶段运行构建

  • 计算所有可能的软件包 ID,排除重复项和无效项
  • 运行实际构建

第一阶段通常在 Linux 机器上运行,因此,macOS M1 配置文件的软件包 ID 计算是在 Linux x86-64 构建机器上完成的。虽然这绝对合法,但它可能会对可能使用条件逻辑检测构建平台的配方产生一些重要的影响。一般来说,强烈不建议使用


# BAD: if tools.OSInto().is_windows:
# BAD: if platform.system() == "Windows":
# BAD: if os.name == 'nt':
if self.settings_build.os == "Windows": # good!
    # build context: os is Windows
    raise ConanInvalidConfiguration("the recipe cannot be built on Windows")

CMake 构建助手和 CMAKE_SYSTEM_PROCESSOR

旧的 CMake 构建助手 并不总是正确设置 CMAKE_SYSTEM_PROCESSOR 变量。虽然我们强烈建议作者迁移到新的 CMake 工具链 解决方案,但在某些配方中可能需要以下代码才能正确交叉编译到 M1

    def _configure_cmake(self):
        cmake = CMake(self)
        ...
        if self.settings.os == "Macos" and self.settings.arch == "armv8":
            cmake.definitions["CMAKE_SYSTEM_PROCESSOR"] = "aarch64"

注意:不幸的是,CMAKE_SYSTEM_PROCESSOR 没有标准。因此,取决于库可能具有的检查,变量的值可以是 aarch64arm64armv8armv8-a

GNU autotools:config.sub 和 config.guess

许多库(尤其是旧库)附带旧的 config.subconfig.guess 脚本。由于 M1 于 2020 年底推出,因此这些库不知道新的架构,需要更新脚本。不幸的是,一些库已经被放弃或停止维护,在这种情况下,可能需要以下解决方法

    def build_requirements(self):
        self.build_requires("gnu-config/cci.20201022")  # require the recent enough GNU config

    # helper to support both single profile and dual profile models
    @property
    def _user_info_build(self):
        return getattr(self, "user_info_build", None) or self.deps_user_info

     def build(self):
        # copy the updated files to the library sources
        shutil.copy(self._user_info_build["gnu-config"].CONFIG_SUB,
                    os.path.join(self._source_subfolder, "config.sub"))
        shutil.copy(self._user_info_build["gnu-config"].CONFIG_GUESS,
                    os.path.join(self._source_subfolder, "config.guess"))

这样,configure 脚本至少应该识别 aarch64-apple-darwin 三元组,允许交叉编译到或来自 M1。