Zig:史上最简单的交叉编译

Sep 14, 2025

我经常会捡到一些 ARM、MIPS 架构的板子,因为性能很捉急,所以一般不会直接用这些板子编译要用到的程序,而是在性能更好的机器上交叉编译,即在 A 架构的机器下构建 B 架构的程序。

传统的 C/C++ 程序的交叉编译需要交叉编译工具链(编译器、链接器、标准库…),还得配置构建工具使用这套工具链,总的来说非常复杂。不过现在我们有了 Zig,给 C/C++ 程序配置交叉编译就像 Go 一样简单。

直接使用 Zig 交叉编译单文件程序

先来个开胃小菜,演示一下 Zig 的命令行界面。

这是一个标准的 C 语言 Hello World:

// Filename - hello_world.c
#include <stdio.h>

int main(void) {
	printf("Hello world!");
	return 0;
}

使用 zig cc 编译到 MIPS:

zig cc -target mips-linux-musleabi hello_world.c -o hello_mips

来看看编译产物:

$ file hello_mips
hello_mips: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, with debug_info, not stripped

对的,就这么简单!Zig 不仅集成了编译器,还集成了链接器、AR、Ranlib 和标准库。你可以用 zig targets 查看当前支持的平台,并轻松的在他们之间切换:

$ zig targets
.{
    .arch = .{
        "amdgcn",
        "arc",
        "arm",
        "armeb",
        "thumb",
        "thumbeb",
        "aarch64",
        "aarch64_be",
...

配置构建系统使用 Zig

常见的项目一般都会使用构建系统构建项目,来看看如何在两种常见的构建系统中使用 Zig。

CMake

这里拿 fastfetch 举例子,因为相对比较简单 (绝对不是我蠢玩不明白 CMake 然后把自己的项目迁到了 Meson)

老样子,先把 fastfetch 拖下来,同时把 zig-cross 也拖下来,等下会用到。

git clone https://github.com/fastfetch-cli/fastfetch
git clone https://github.com/mrexodia/zig-cross.git

进入 fastfetch 的目录里,然后把 zig-cross 里的 /cmake 目录和某个 <xxx>.cmake 复制到这个目录里。

cd fastfetch
cp -rf ../zig-cross/cmake ../zig-cross/<xxx>.cmake .

然后重命名 <xxx>.cmake 为你期望的平台,例如:

cp <xxx>.cmake mipsel-linux-musleabi.cmake

然后生成构建目录,尝试构建一下:

cmake -B build-mipsel -G Ninja --toolchain mipsel-linux-musleabi.cmake
cmake --build build-mipsel

然后不出所料… 报错了:

[1/224] Building C object CMakeFiles/libfastfetch.dir/src/common/format.c.o
FAILED: CMakeFiles/libfastfetch.dir/src/common/format.c.o
/home/leo/fastfetch/cmake/zig-cc -target mipsel-linux-musleabi -DFF_HAVE_DBUS=1 -DFF_HAVE_ELF=1 -DFF_HAVE_GLOB=1 -DFF_HAVE_LIBZFS=1 -DFF_HAVE_LINUX_VIDEODEV2=1 -DFF_HAVE_LINUX_WIRELESS=1 -DFF_HAVE_PIPE2 -DFF_HAVE_STATX -DFF_HAVE_THREADS=1 -DFF_HAVE_TIMEDJOIN_NP=1 -DFF_HAVE_UTMPX=1 -DFF_HAVE_WCWIDTH -DFF_HAVE_WORDEXP=1 -DFF_HAVE_ZLIB=1 -DFF_PACKAGES_DISABLE_LIST=FF_PACKAGES_FLAG_WINGET_BIT -D_ATFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -D_TIME_BITS=64 -D_XOPEN_SOURCE -D__STDC_WANT_LIB_EXT1__ -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -I/usr/include -I/home/leo/fastfetch/build-mipsel -I/home/leo/fastfetch/src -Wall -Wextra -Wconversion -Werror=uninitialized -Werror=return-type -Werror=vla -Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion -O2 -g -DNDEBUG -std=gnu11 -MD -MT CMakeFiles/libfastfetch.dir/src/common/format.c.o -MF CMakeFiles/libfastfetch.dir/src/common/format.c.o.d -o CMakeFiles/libfastfetch.dir/src/common/format.c.o -c /home/leo/fastfetch/src/common/format.c
In file included from /home/leo/fastfetch/src/common/format.c:1:
In file included from /home/leo/fastfetch/src/fastfetch.h:5:
/usr/include/stdint.h:26:10: fatal error: 'bits/libc-header-start.h' file not found
   26 | #include <bits/libc-header-start.h>
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

你可以看到编译器在尝试使用主机的 libc,which is bad。很明显是由于 -I/usr/include 这个选项,但是我没找到是啥把这玩意儿带进来的,摸索出来的解决办法是关掉所有依赖:

#####################
# Configure options #
#####################

include(CMakeDependentOption)

cmake_dependent_option(ENABLE_VULKAN "Enable vulkan" OFF "LINUX OR APPLE OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku OR GNU" OFF)
...

下面的 Zlib 也要关掉。

option(ENABLE_ZLIB "Enable zlib" OFF)

再 build 一遍,还是报错了233:

[39/223] Building C object CMakeFiles/libfastfetch.dir/src/detection/version/version.c.o
FAILED: CMakeFiles/libfastfetch.dir/src/detection/version/version.c.o
/home/leo/fastfetch/cmake/zig-cc -target mipsel-linux-musleabi -DFF_HAVE_GLOB=1 -DFF_HAVE_LINUX_VIDEODEV2=1 -DFF_HAVE_LINUX_WIRELESS=1 -DFF_HAVE_PIPE2 -DFF_HAVE_STATX -DFF_HAVE_UTMPX=1 -DFF_HAVE_WCWIDTH -DFF_HAVE_WORDEXP=1 -DFF_PACKAGES_DISABLE_LIST=FF_PACKAGES_FLAG_WINGET_BIT -D_ATFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -D_TIME_BITS=64 -D_XOPEN_SOURCE -D__STDC_WANT_LIB_EXT1__ -I/home/leo/fastfetch/build-mipsel -I/home/leo/fastfetch/src -Wall -Wextra -Wconversion -Werror=uninitialized -Werror=return-type -Werror=vla -Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion -O2 -g -DNDEBUG -std=gnu11 -MD -MT CMakeFiles/libfastfetch.dir/src/detection/version/version.c.o -MF CMakeFiles/libfastfetch.dir/src/detection/version/version.c.o.d -o CMakeFiles/libfastfetch.dir/src/detection/version/version.c.o -c /home/leo/fastfetch/src/detection/version/version.c
/home/leo/fastfetch/src/detection/version/version.c:74:20: error: expansion of date or time macro is not reproducible [-Werror,-Wdate-time]
   74 |     .compileTime = __DATE__ ", " __TIME__,
      |                    ^
/home/leo/fastfetch/src/detection/version/version.c:74:34: error: expansion of date or time macro is not reproducible [-Werror,-Wdate-time]
   74 |     .compileTime = __DATE__ ", " __TIME__,
      |                                  ^
2 errors generated.

这似乎是因为 Zig 默认的警告开的比较高,很多警告都会被当成错误,手动忽略即可:

echo 'add_compile_options(-Wno-error=date-time)' >> mipsel-linux-musleabi.cmake

总算成功了:

$ file build-mipsel/flashfetch
build-mipsel/flashfetch: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), statically linked, with debug_info, not stripped

上实机跑跑看:

Meson

Meson 是一个类似于 CMake 的构建系统,不过相对简单很多。我自己的项目用到了 Meson,把他 clone 下来看看:

git clone --depth 1 https://github.com/diredocks/zzz.git
cd zzz

你会发现有个 /cross 目录,里面的文件就相当于 CMake 的交叉编译配置,随便打开一个看看:

[binaries]
c = ['zig', 'cc']
cpp = ['zig', 'c++']
ar = ['zig', 'ar']
ranlib = ['zig', 'ranlib']
lib = ['zig', 'lib']
dlltool = ['zig', 'dlltool']
pkg-config = 'pkg-config'

[host_machine]
system = 'linux'
cpu_family = 'mips'
cpu = 'mipsel'
endian = 'little'

[built-in options]
c_args = ['-target', 'mipsel-linux-musleabi']
c_link_args = ['-target', 'mipsel-linux-musleabi']

其他目标平台的也同理,更改 -target 即可。

要开始构建,使用以下命令:

meson setup build-mipsel --cross-file cross/mipsel-linux-musleabi.toml
meson compile -C build-mipsel

又 TM 报错了,这次是我们依赖的 pcap 找不到定义:

[2/15] Compiling C object zzz.p/src_main.c.o
FAILED: zzz.p/src_main.c.o
zig cc -Izzz.p -I. -I.. -I../include -I../src -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -I/usr/include/libnl3 -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -std=gnu23 -O3 -target mipsel-linux-musleabi -MD -MQ zzz.p/src_main.c.o -MF zzz.p/src_main.c.o.d -o zzz.p/src_main.c.o -c ../src/main.c
In file included from ../src/main.c:8:
../include/utils/device.h:5:10: fatal error: 'pcap.h' file not found
    5 | #include <pcap.h>
      |          ^~~~~~~~
1 error generated.

交叉编译需要程序的依赖库也是目标平台版本的,所以我们现在得去自己编译 libpcap:

# 这里假定你已经下好了 libpcap 的源码包
CC="zig cc -target mipsel-linux-musleabi" \
AR="zig ar" \
RANLIB="zig ranlib" \
./configure --host=x86_64-linux --with-pcap=linux --prefix=$(pwd)

make -j$(nproc) && make install
export PCAP_PATH=$(pwd)/lib/pkgconfig/

回到我们的项目目录,Meson 会调用 pkg-config 寻找 pcap,所以 pkg-config 配置一下查找路径:

export PKG_CONFIG_LIBDIR=$PCAP_PATH

然后重新生成构建文件,并构建:

meson setup --wipe build-mipsel --cross-file cross/mipsel-linux-musleabi.toml
meson compile -C build-mipsel

总算好了:

$ file build-mipsel/zzz
build-mipsel/zzz: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-mipsel-sf.so.1, with debug_info, not stripped

小结

用 Go 的理由又少了一个,可能我应该多赚钱买点高配垃圾子。