用 Zig 交叉编译 C/C++ 项目

什么是交叉编译?

  在上一部分中,我们了解了如何使用 Zig 为编译器运行的同一目标生成 C/C++ 项目的构建。有了有效的交叉编译设置,你就能从 x86_64 Windows 中创建 ARM Linux 可执行文件。

  当您需要发布一个可在多个平台上运行的应用程序时,交叉编译就显得尤为重要:有了 Zig,您就可以在一台机器上创建所有的发布工件!

Zig 中的交叉编译支持

  对于 Zig 来说,交叉编译是一个主要的使用案例,因此从捆绑适用于所有主要平台的 libc 实现开始,Zig 投入了大量精力使其成为一种无缝体验。

  如果你好奇,可以阅读安德鲁-凯利(Andrew Kelley)的这篇博文,详细了解 Zig 如何扩展 clang 以简化交叉编译。

  说到 macOS,Zig 甚至有一个定制的链接器,可以为英特尔和苹果 Silicon M1 进行交叉编译,这一点甚至连 lld(LLVM 的链接器)都做不到。正因为如此,在撰写本文时,Zig 是唯一能为 Apple Silicon 进行交叉编译和交叉签名(即从另一个平台执行代码设计)的 C/C++ 编译器。

关于构建脚本的可移植性...

  遗憾的是,C/C++ 项目几乎从未考虑过交叉编译。例如,一个项目可能在 macOS 和 Linux 上都能编译,因而具有可移植性,但几乎所有的编译脚本都依赖于脆弱的能力检查,需要人工干预才能迫使编译脚本配合交叉编译过程。

  更重要的是,Makefile 不会以声明的方式列出需求,因此阅读所有内容是了解构建脚本依赖于哪些其他工具的唯一途径。其他构建系统在这方面可能做得更好,但总的来说,你不能指望获得与上一篇文章中相同的无缝体验。

  正如我们现在看到的,Redis 也是如此。

交叉编译 Redis

  如果您按照上一篇文章中的步骤,为本地目标(即编译过程实际发生的机器)编译了 Redis,那么您需要运行 make distclean,以避免因陈旧资产未被 Make 正确作废而导致的混乱问题。

  下面是一些交叉编译调用的示例:

  针对英特尔 macOS

make CC="zig cc -target x86_64-macos" CXX="zig c++ -target x86_64-macos" AR="zig ar" RANLIB="zig ranlib" USE_JEMALLOC=no USE_SYSTEMD=no

  以 x86-64 Linux(和 musl libc)为目标

make CC="zig cc -target x86_64-linux-musl" CXX="zig c++ -target x86_64-linux-musl" AR="zig ar" RANLIB="zig ranlib" USE_JEMALLOC=no USE_SYSTEMD=no

  让我们来分析一下你刚才看到的内容。第一个值得注意的变化是 -target 开关。这部分不言自明:因为我们要编译到另一个目标,所以必须指定是哪个目标。第二个显著区别是出现了一些新的重载,这是怎么回事呢?

  仔细阅读构建 Redis 所涉及的 Makefile(是的,有多个),你会发现 redis-cli 和 redis-server 共享 hiredis,这是一个实现通信协议解析器的库。因此,hiredis 被编译为静态库,并包含在两个可执行文件的编译过程中。事实上,Redis 的另一个依赖库 lua 也是如此。

  这种编译代码的方法依赖于两个工具:Ar 和 ranlib。事实上,在上一篇文章中也出现过这种情况,当时我们正在进行本机编译,但由于我们已经需要大量的系统工具(如 build-essential 或 Xcode 命令行工具),所以我们可以不必了解这些。但现在我们要为另一个目标构建,因此这些命令需要与交叉编译过程配合,这就要求我们使用 Zig 提供的版本。

  现在让我们来谈谈另一个重写,即 USE_JEMALLOC。Redis 默认在 x86-64 Linux 上使用 jemalloc。不幸的是,jemalloc 是一个使用 CMake 的 C++ 库,这让构建过程变得非常复杂。为了简洁起见,我强制使用了 libc 中的 vanilla malloc,但如果你了解 CMake,还是有可能实现跨编译的。请读者自行解决。

  编译成功了,我们就大功告成了吗?其实不然。Makefile 中还有一些细节需要我们了解。

修复 Makefile

  让我们看看 Redis 主 Makefile (src/Makefile) 中最重要的两行。

uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')

  这几行使用 shell 命令来获取当前机器的操作系统和架构。在交叉编译时,你可以看到这可能会造成问题。

  在这种情况下,解决办法是在命令行中提供重写,但首先我们应该看看这些变量是如何使用的,以便更好地理解用什么来重写它们。

# Default allocator defaults to Jemalloc if it's not an ARM
MALLOC=libc
ifneq ($(uname_M),armv6l)
ifneq ($(uname_M),armv7l)
ifeq ($(uname_S),Linux)
    MALLOC=jemalloc
endif
endif
endif

# ...

ifeq ($(USE_JEMALLOC),yes)
    MALLOC=jemalloc
endif

ifeq ($(USE_JEMALLOC),no)
    MALLOC=libc
endif

  本例说明了 uname_M 包含 CPU 架构,而 uname_S 包含操作系统名称。我们还可以看到,该变量与一些关键字的匹配很松散,这意味着我们在覆盖时不必太精确。这项检查对我们来说并不重要,因为我们已经在命令行中禁用了 jemalloc,但它揭示了两个重要变量的使用模式。在 Makefile 中使用操作系统名称和版本的方式并不是标准化的,你需要根据具体情况了解覆盖它们的正确方式。

  Makefile 还包含另一个奇怪的能力检查:

# Detect if the compiler supports C11 _Atomic
C11_ATOMIC := $(shell sh -c 'echo "\#include <stdatomic.h>" > foo.c; \
    $(CC) -std=c11 -c foo.c -o foo.o > /dev/null 2>&1; \
    if [ -f foo.o ]; then echo "yes"; rm foo.o; fi; rm foo.c')
ifeq ($(C11_ATOMIC),yes)
    STD+=-std=c11
else
    STD+=-std=c99
endif

  在这里,你可以看到 Makefile 如何尝试编译一个导入了 stdatomic.h 的 C 程序,并使用退出代码来测试是否支持 C11 功能。在我们的例子中,我们可以肯定地说,Zig 支持 C11,因此可以用命令行覆盖来替代这一检查,或者我们可以不做检查,因为它总是会成功的。

  让我们看看 Makefile 中的最后一段话:

# If 'USE_SYSTEMD' in the environment is neither "no" nor "yes", try to
# auto-detect libsystemd's presence and link accordingly.
ifneq ($(USE_SYSTEMD),no)
    LIBSYSTEMD_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libsystemd && echo $$?)
# If libsystemd cannot be detected, continue building without support for it
# (unless a later check tells us otherwise)
ifeq ($(LIBSYSTEMD_PKGCONFIG),0)
    BUILD_WITH_SYSTEMD=yes
    LIBSYSTEMD_LIBS=$(shell $(PKG_CONFIG) --libs libsystemd)
endif
endif

  如果是为 Linux 编译,Redis 会尝试检测您是否需要 SystemD 支持,但由于我们是交叉编译,因此需要明确说明我们的意图。

  为了简洁起见,我只是在编译命令中禁用了 SystemD 支持,但如果你想启用它,就必须获取正确的头文件,并将其提供给编译过程,这可能还需要重写上面代码片段中的一些逻辑。

  还有更多依赖 shell 命令执行的检查示例,我把它们留给读者自己去寻找。

  根据我们在 Makefile 中找到的内容,这是一组交叉编译 Linux 的重载示例: uname_S="Linux" uname_M="x86_64" C11_ATOMIC=yes USE_JEMALLOC=no USE_SYSTEMD=no。

最后的命令

make CC="zig cc -target x86_64-linux-musl" CXX="zig c++ -target x86_64-linux-musl" AR="zig ar" RANLIB="zig ranlib" uname_S="Linux" uname_M="x86_64" C11_ATOMIC=yes USE_JEMALLOC=no USE_SYSTEMD=no

  无论你使用的是哪个操作系统,都可以运行相同的命令来发布 Redis。

Windows

  Redis 并不针对 Windows,但如果你有通过 msys、cygwin 或 mingw 制作的软件,仍可使用上述命令在 Windows 下交叉编译到 Linux 或 Mac。

下一步是什么?

  处理构建脚本很快就会失控,即使是在 Redis 这样相当严谨的项目中,你也能看到多个构建系统是如何交织在一起,使一切都变得毫无必要的笨拙。

  在下一篇文章中,我们将介绍如何摆脱所有这些构建系统,只依赖 zig build。这不仅能让你更容易理解构建过程,还能让交叉编译完全无缝进行,并将你从 Make、CMake 等所需的所有系统依赖关系中解放出来。

  这意味着我们甚至不需要构建必备程序、XCode 或 MSVC 就能构建 Redis。

  迄今为止,Windows 并没有受到太多的青睐:Redis 无法在 Windows 上运行,虽然你可以使用 Windows 为其他操作系统交叉编译,但对 Make、CMake、shell 脚本等的依赖并没有带来真正的帮助。在下一篇文章中,这一切都将改变。

  可重复性脚注
  Zig 0.8.1,Redis commit be6ce8a。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值