- 编程语言
- 语言运行时
- 标准库
- 编译器
- 并发编程
【免费下载链接】otp
Erlang/OTP
本文以 Erlang/OTP 官方 HOWTO 文档 HOWTO/INSTALL-RASPBERRYPI3.md 为骨架,系统讲解如何在 macOS(Mojave)宿主机上为 Raspberry Pi 3 构建交叉编译工具链、交叉编译第三方依赖(zlib / openssl / ncurses)、填充 sysroot,并最终通过otp_build配合仓库自带的 xcomp 交叉配置产出树莓派 3 可运行的 Erlang/OTP 发布包。读完本文,你将掌握一套可复现的"工具链 → 依赖 → sysroot → OTP 交叉编译"四段式流程,并能读懂仓库中 xcomp/erl-xcomp-armv8-rpi3-linux-gnueabihf.conf 等交叉配置文件的每个变量含义。
一、背景与适用场景
当你手上有一台树莓派 3(或同类 armv7l/armv8 32 位 Linux 目标),而官方尚未为它提供最新 OTP 版本的二进制包时,交叉编译是让最新 OTP(或 master 分支)跑起来的最直接途径。本文面向的是"在 macOS 上为树莓派 3 交叉编译 Erlang/OTP"这一场景,原文基于 macOS Mojave 10.14.3 与 Raspberry Pi 3 Model B Rev 1.2 验证。
整个过程耗时数小时,核心瓶颈在于第一步:从零构建交叉编译工具链。工具链就绪后,交叉编译所有库依赖、创建 sysroot 文件系统,最后用新工具链和 sysroot 交叉编译 Erlang/OTP。由于涉及大量第三方软件包的版本组合,实际执行中可能遇到文档未覆盖的报错,需要自行修复——正如原文作者所言:"构建工具链并不是一件有趣的事"。
动手之前,建议先通读仓库中的两份配套文档:
- HOWTO/INSTALL.md:Erlang/OTP 的常规构建与安装指南,介绍
configure/make与otp_build两条构建路线; - HOWTO/INSTALL-CROSS.md:Erlang/OTP 交叉编译的系统性说明,包含全部交叉配置变量定义。
已测试的配置
| 项目 | 版本 |
|---|---|
| 宿主机操作系统 | macOS Mojave 10.14.3 |
| 目标硬件 | Raspberry Pi 3 Model B Rev 1.2 |
| 工具链生成器 | Crosstools-NG 1.23.0_3 |
build = x86_64-apple-darwin18.2.0 host = x86_64-apple-darwin18.2.0 target = armv8-rpi3-linux-gnueabihf提示:树莓派上的
/proc/device/tree/model文件包含机型信息,可用于确认目标硬件型号。
二、准备阶段:安装 crosstool-ng 与创建磁盘映像
安装 crosstool-ng(步骤 1-2)
macOS 下通过 Homebrew 安装 crosstool-ng,同时安装其脚本依赖:
$ brew install crosstool-ng $ brew install grep --default-names # crosstools-ng 脚本需要 $ brew install md5sha1sum # crosstools-ng populate 脚本需要安装后需要为工具链脚本补上可执行权限(不同版本路径略有差异):
$ chmod 744 /usr/local/Cellar/crosstool-ng/1.23.0_3/lib/crosstool-ng-1.23.0/scripts/crosstool-NG.sh创建大小写敏感的磁盘映像(步骤 3)
在 Disk Utility 中通过File -> New Image -> Blank Image...创建两个大小写敏感的磁盘映像,格式选Mac OS Extended (Case-sensitive, Journaled):
/Volumes/xtools-build-env 15 GB /Volumes/xtools 500 MB- 第一个映像(15 GB)存放构建工具链期间的所有源码与目标文件;
- 第二个映像(500 MB)存放编译好的工具链。
大小写敏感是 GNU 工具链源码构建的硬性要求(源码树中存在仅大小写不同的同名文件),这一步不能省。
三、构建交叉工具链(The Long Road)
环境设置(步骤 4)
$ ulimit -n 1024提高文件描述符上限,避免多进程并行编译时"Too many open files"。
检查目标系统(步骤 5)
在树莓派上记录目标系统的关键组件版本,工具链中各组件版本必须与目标系统匹配:
$ uname -a Linux raspberrypi 4.9.35-v7+ #1014 SMP Fri Jun 30 14:47:43 BST 2017 armv7l GNU/Linux $ ld -v GNU ld (GNU Binutils for Raspbian) 2.25 $ ldd --version ldd (Debian GLIBC 2.19-18+deb8u10) 2.19务必使用目标系统上的实际版本!例如本例中目标系统是 Debian 8 衍生系统(GLIBC 2.19),则工具链中的 glibc 也应对应选择 2.19。OSDev Wiki 的 "Cross-Compiler Successful Builds" 页面记录了各目标组合的成功构建版本矩阵,可作为选型参考。
配置 crosstool-ng(步骤 6)
$ cd /Volumes/xtools-build-env $ ct-ng armv8-rpi3-linux-gnueabihf $ ct-ng menuconfigct-ng armv8-rpi3-linux-gnueabihf会载入与 OTP 仓库中 xcomp/erl-xcomp-armv8-rpi3-linux-gnueabihf.conf 对应目标三元组(triplet)相同的预置样本,随后用menuconfig逐项微调。
Path and misc options → crosstool-NG behavior(步骤 7)
[*] Use obsolete features [*] Debug crosstool-NG [ ] Pause between every steps [*] Save intermediate steps [*] gzip saved states [*] Interactive shell on failed commands关键点:勾选Save intermediate steps(保存中间步骤)与Interactive shell on failed commands(失败时进入交互 shell)。这样当某个构建步骤失败时,可以修复问题后从断点继续,而不必从头重来。
Paths(步骤 8)
- Local tarballs directory:
/Volumes/xtools-build-env/src - Working directory:
/Volumes/xtools-build-env/.build - Prefix directory:
/Volumes/xtools/${CT_TARGET}
Extracting(步骤 9)
勾选[*] Stop after extracting tarballs,让构建过程在解压完所有源码包后立即停止。这在"需要在正式构建前手工修补源码已知问题"时非常有用。
Operating System(步骤 10)
选择Linux kernel version (4.9.20),与目标系统内核 4.9.35 同系列。
Binary utilities(步骤 11)
选择bintutils version (2.28)。
C-library(步骤 12)
选择glibc version (2.19 (OBSOLETE))——注意 crosstool-ng 会把 2.19 标记为 OBSOLETE(过时),但这正对应目标树莓派系统上的 GLIBC 2.19,交叉编译产物才能与目标系统动态库 ABI 兼容。
ct-ng 常用命令:步进式构建
交叉工具链构建是多步骤流水线,crosstool-ng 支持按步执行、断点续跑:
- 列出所有构建步骤:
$ ct-ng list-steps Available build steps, in order: - companion_tools_for_build - companion_libs_for_build - binutils_for_build - companion_tools_for_host - companion_libs_for_host - binutils_for_host - cc_core_pass_1 - kernel_headers - libc_start_files - cc_core_pass_2 - libc - cc_for_build - cc_for_host - libc_post_cc - companion_libs_for_target - binutils_for_target - debug - test_suite - finish- 只重跑单个步骤(以
companion_libs_for_host为例):
$ ct-ng companion_libs_for_host- 从某个步骤开始向后执行(
+后缀):
$ ct-ng companion_libs_for_host+- 从开头执行到某个步骤为止(
+前缀):
$ ct-ng +companion_libs_for_host正式构建(步骤 13-17)
首次构建(会按配置在解压 tarball 后停下):
$ ct-ng build针对已知源码问题打补丁——本例中 gettext 0.19.8.1 需要重新生成 autoconf 脚本:
$ pushd .build/src/gettext-0.19.8.1/ $ autoreconf $ popd再次进入 menuconfig,取消"解压后停止":
$ ct-ng menuconfig取消勾选:[ ] Stop after extracting tarballs
从断点继续构建:
$ ct-ng build构建完成后将工具链 bin 目录加入 PATH:
$ export PATH=/Volumes/xtools/armv8-rpi3-linux-gnueabihf/bin:$PATH验证工具链(步骤 18)
$ cat > test.c $ int main() { printf("Hello, world!\n"); return 0; } <Ctrl+D> $ armv8-rpi3-linux-gnueabihf-gcc -o test test.c如果armv8-rpi3-linux-gnueabihf-gcc能正常编译出可执行文件,说明工具链可用(file test应显示 ARM 架构)。注意 test.c 中需包含<stdio.h>头文件才能通过编译,原文此处示例省略了 include。
四、交叉编译 OTP 第三方依赖(步骤 19-22)
工具链就绪后,交叉编译 Erlang/OTP 运行时依赖的第三方库。建立两个目录:
$ mkdir local # prefix 目录:安装交叉编译的依赖 $ mkdir 3pps # OTP 依赖源码目录 $ cd 3ppszlib(步骤 20)
$ wget http://zlib.net/zlib-1.2.13.tar.gz $ tar xf zlib-1.2.13.tar.gz $ pushd zlib-1.2.13 $ CHOST=armv8-rpi3-linux-gnueabihf ./configure --prefix=/Volumes/xtools-build-env/local $ make $ make install $ popdCHOST环境变量让 zlib 的 configure 使用交叉编译工具前缀armv8-rpi3-linux-gnueabihf-,产物安装到统一 prefix 目录/Volumes/xtools-build-env/local。
openssl(步骤 21)
$ wget http://openssl.org/source/openssl-1.1.1b.tar.gz $ tar xf openssl-1.1.1b.tar.gz $ pushd openssl-1.1.1b $ ./Configure linux-generic32 --prefix=/Volumes/xtools-build-env/local \ --openssldir=/Volumes/xtools-build-env/local/openssl \ --cross-compile-prefix=armv8-rpi3-linux-gnueabihf- $ make $ make install $ popdlinux-generic32是 OpenSSL 针对 32 位 ARM Linux 的通用配置目标,--cross-compile-prefix指定工具链前缀。目标系统上必须存在与之兼容的 OpenSSL 运行库,否则 OTP 的crypto、ssl应用在树莓派上无法加载(参见 HOWTO/INSTALL.md 中--with-ssl/--without-ssl相关说明)。
ncurses(步骤 22)
ncurses 的交叉编译最繁琐,需要打补丁并显式给出 build/host 三元组:
$ wget http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz $ tar xf ncurses-5.9.tar.gz $ pushd ncurses-5.9 $ wget https://gist.githubusercontent.com/peterdmv/1068b2f9e1fec6e1330ad62ed87461ad/\ raw/065597b63654ed6a9f28d02fdfbca844413847ad/ncurses-5.9.patch $ patch -p0 < ncurses-5.9.patch $ ./configure --build=x86_64-apple-darwin18.2.0 --host=armv8-rpi3-linux-gnueabihf \ --without-ada --without-cxx --without-cxx-binding --without-manpages \ --without-progs --without-tests --prefix=/usr --libdir=/lib \ --with-build-cc="gcc -D_GNU_SOURCE" --with-shared $ make $ make DESTDIR=/Volumes/xtools-build-env/local install $ popd参数解读:
--build=x86_64-apple-darwin18.2.0:构建机(macOS)三元组,与第三节中的 build 值一致;--host=armv8-rpi3-linux-gnueabihf:目标三元组;--without-ada/cxx/...:裁剪不需要的语言绑定与工具程序,减少交叉编译面;--prefix=/usr --libdir=/lib:模拟目标系统布局(Raspbian 将 ncurses 库放在/lib);--with-build-cc="gcc -D_GNU_SOURCE":构建工具使用宿主机 gcc,避免引导阶段循环依赖;--with-shared:生成共享库;make DESTDIR=... install:将产物安装到暂存目录,便于下一步 populate 进 sysroot。
五、填充 sysroot(步骤 23-24)
将上一节安装在/Volumes/xtools-build-env/local下的依赖以及目标系统的运行时文件合并成 sysroot:
$ chmod 755 /Volumes/xtools/armv8-rpi3-linux-gnueabihf/bin $ chmod 755 /Volumes/xtools/armv8-rpi3-linux-gnueabihf/bin/armv8-rpi3-linux-gnueabihf-populate $ gsed -i 's/"sed"/"gsed"/g' \ /Volumes/xtools/armv8-rpi3-linux-gnueabihf/bin/armv8-rpi3-linux-gnueabihf-populate $ chmod 555 /Volumes/xtools/armv8-rpi3-linux-gnueabihf/bin $ chmod 555 /Volumes/xtools/armv8-rpi3-linux-gnueabihf/bin/armv8-rpi3-linux-gnueabihf-populate第一组命令解决两个 macOS 特有问题:
bin目录及 populate 脚本需要可执行位;populate 脚本内部硬编码调用 BSD 版sed,需替换为 GNU sed(gsed,即前面安装的brew install grep --default-names同批安装的工具),因此用gsed -i将脚本中的"sed"全部替换为"gsed"。
执行 populate 并导出 sysroot 环境变量:
$ armv8-rpi3-linux-gnueabihf-populate -s /Volumes/xtools-build-env/local \ -d /Volumes/xtools-build-env/sysroot $ export RPI_SYSROOT=/Volumes/xtools-build-env/sysroot-s指定源目录,-d指定目标 sysroot 目录。RPI_SYSROOT将被后面的 xcomp 配置文件引用。
六、交叉编译 Erlang/OTP(步骤 25)
回到 OTP 源码树顶层(即$ERL_TOP),依次执行三步:
$ ./otp_build configure --xcomp-conf=./xcomp/erl-xcomp-armv8-rpi3-linux-gnueabihf.conf $ ./otp_build boot -a $ ./otp_build release -a /Volumes/xtools-build-env/otp_22.0三步分别对应:
- configure:传入交叉编译配置文件。
otp_build脚本会在内部加载该文件(参见 otp_build 脚本中对--xcomp-conf=*参数的处理逻辑:读取文件、source其中变量、将erl_xcomp_build/erl_xcomp_host转换为--build=/--host=传给 configure,其余变量经env_to_config_flags转成 configure 参数),并同时为构建机 bootstrap 系统和交叉目标系统生成两份配置; - boot -a:先为构建机构建一个最小 bootstrap Erlang 系统(用于编译 Erlang 源码),再对目标系统执行交叉构建。
-a表示构建全部应用; - release -a:将交叉构建产物组装为发布目录(等价于
make release的otp_build版本)。之后还需要在宿主机或目标机上手工执行Install脚本完成安装(详见 HOWTO/INSTALL-CROSS.md 的 "Installing Manually" 一节)。
关于otp_build与configure/make两条路线的差异,HOWTO/INSTALL-CROSS.md 明确指出:otp_build步骤更少但灵活性较低,且其默认配置与直接运行configure不同(例如目前会默认追加--disable-dynamic-ssl-lib)。若改用configure直接交叉编译,则不能使用--xcomp-conf参数,只能以--host=<HOST> --build=<BUILD> <VAR>=<VALUE>形式传参,且wx应用在交叉编译时会被自动禁用。
七、深入解析仓库自带的树莓派 3 交叉配置文件
仓库中 xcomp/erl-xcomp-armv8-rpi3-linux-gnueabihf.conf 正是第六节使用的配置文件(作者 Péter Dimitrov,验证环境 macOS High Sierra 10.13.2 + Raspberry Pi 3 Model B Rev 1.2)。它也是第三节 crosstool-ng 目标三元组armv8-rpi3-linux-gnueabihf的直接来源。完整内容如下:
## -- Variables for `otp_build' Only ------------------------------------------- erl_xcomp_build=guess # 构建机三元组:guess 自动探测 erl_xcomp_host=armv8-rpi3-linux-gnueabihf # 目标三元组 #erl_xcomp_configure_flags= # 额外 configure 参数 ## -- Cross Compiler and Other Tools ------------------------------------------- CC=armv8-rpi3-linux-gnueabihf-gcc #CFLAGS= #STATIC_CFLAGS= #CFLAG_RUNTIME_LIBRARY_PATH= CPP=armv8-rpi3-linux-gnueabihf-cpp #CPPFLAGS= CXX=armv8-rpi3-linux-gnueabihf-g++ #CXXFLAGS= LD=armv8-rpi3-linux-gnueabihf-ld LDFLAGS="-L$RPI_SYSROOT/lib" # 链接时查找 sysroot 库 #LIBS= ## -- *D*ynamic *E*rlang *D*river Linking -- (DED_LD / DED_LDFLAGS / ...) ## -- Large File Support -- (LFS_CFLAGS / LFS_LDFLAGS / LFS_LIBS) ## -- Other Tools -- RANLIB=armv8-rpi3-linux-gnueabihf-ranlib AR=armv8-rpi3-linux-gnueabihf-ar #GETCONF= ## -- Cross System Root Locations ---------------------------------------------- erl_xcomp_sysroot="$RPI_SYSROOT" # 交叉编译环境系统根 #erl_xcomp_isysroot= # include 系统根,默认等于 sysroot ## -- Optional Feature, and Bug Tests ------------------------------------------ #erl_xcomp_after_morecore_hook= #erl_xcomp_bigendian= #erl_xcomp_double_middle_endian= #erl_xcomp_clock_gettime_cpu_time= #erl_xcomp_getaddrinfo= #erl_xcomp_gethrvtime_procfs_ioctl= #erl_xcomp_dlsym_brk_wrappers= #erl_xcomp_kqueue= #erl_xcomp_linux_clock_gettime_correction= #erl_xcomp_linux_nptl= #erl_xcomp_linux_usable_sigaltstack= #erl_xcomp_linux_usable_sigusrx= #erl_xcomp_poll= #erl_xcomp_putenv_copy= #erl_xcomp_reliable_fpe= #erl_xcomp_posix_memalign= #erl_xcomp_code_model_small=核心要点:
- 工具链变量:由于 crosstool-ng 生成的工具全部带
armv8-rpi3-linux-gnueabihf-前缀,且该前缀与erl_xcomp_host一致,按 HOWTO/INSTALL-CROSS.md 的说明,CC/CPP/CXX/LD/AR/RANLIB本可以省略;本文件显式写出属于"显式声明"的稳妥做法。 - LDFLAGS 引用
$RPI_SYSROOT:链接第三方库时从 sysroot 的/lib目录查找——这正是第五节export RPI_SYSROOT=...的意义所在,也解释了为何依赖必须安装在--libdir=/lib布局下。 - erl_xcomp_sysroot:
crypto、odbc、ssh、ssl四个应用必须依赖 sysroot 才能构建;未设置 sysroot 时这些应用会被跳过(详见 HOWTO/INSTALL-CROSS.md 的 "Cross System Root Locations" 一节)。本文件将 sysroot 指向 populate 出的目录。 - DED(动态 Erlang 驱动)与 LFS(大文件支持):两组变量要么全部设置、要么全部不设置,保持默认即可。
八、交叉配置变量速查:哪些可以"不用管"
交叉配置文件里不能定义任意变量,只有 xcomp/erl-xcomp-vars.sh 中登记的那批erl_xcomp_*变量能保证在整个 configure 过程中全局可见(其余变量需以 configure 参数或环境变量方式传入)。完整清单为:erl_xcomp_sysroot、erl_xcomp_isysroot、erl_xcomp_bigendian、erl_xcomp_double_middle_endian、erl_xcomp_linux_clock_gettime_correction、erl_xcomp_linux_nptl、erl_xcomp_linux_usable_sigusrx、erl_xcomp_linux_usable_sigaltstack、erl_xcomp_poll、erl_xcomp_kqueue、erl_xcomp_putenv_copy、erl_xcomp_reliable_fpe、erl_xcomp_getaddrinfo、erl_xcomp_gethrvtime_procfs_ioctl、erl_xcomp_clock_gettime_cpu_time、erl_xcomp_after_morecore_hook、erl_xcomp_dlsym_brk_wrappers、erl_xcomp_posix_memalign、erl_xcomp_code_model_small。
其中"可选特性与缺陷测试"这一类(第八节末尾的大段注释)在交叉编译时无法自动探测,configure 会为使用默认值的变量打印警告。通常不需要设置它们,但若设置错误可能导致难以察觉的运行时错误,务必确认目标系统行为后再修改。几个值得关注的默认值(来自 HOWTO/INSTALL-CROSS.md):
erl_xcomp_linux_nptl:Linux 默认yes,要求目标系统具备 NPTL 线程库(内核通常 ≥ 2.6);erl_xcomp_linux_clock_gettime_correction:Linux 默认yes,要求clock_gettime(CLOCK_MONOTONIC, _)可用;erl_xcomp_bigendian:无默认值,若 configure 无法自动判断字节序将直接失败,需手工指定(本例目标为小端 ARM,可设为no);erl_xcomp_poll:Darwin/macOS 默认no,其余平台默认yes,目标系统需有可处理设备文件的poll()实现;erl_xcomp_kqueue:默认no,若为no且目标系统既无epoll()也无/dev/poll,内核 poll 特性会被禁用。
更多示例可参考仓库中的 xcomp/erl-xcomp-TileraMDE2.0-tilepro.conf 与 xcomp/erl-xcomp.conf.template(后者收录全部可用变量及注释)。
九、交叉编译后的收尾:测试与部署
交叉构建产出的发布目录还需要在目标机上完成Install流程。根据 HOWTO/INSTALL-CROSS.md,可将整个发布目录打包拷贝到树莓派,进入$RELEASE_DIR后执行:
$ cd <ABSOLUTE_INSTALL_DIR_ON_TARGET> $ ./Install [-minimal|-sasl] <ABSOLUTE_INSTALL_DIR_ON_TARGET>-minimal:仅启动kernel与stdlib的最小安装(make install的默认行为);-sasl:额外启动sasl应用;- 若在构建机上运行
Install则需要加-cross参数。
交叉编译系统的测试遵循 HOWTO/INSTALL-CROSS.md 的 "Testing the cross compiled system" 流程:先在构建机make release_tests(或./otp_build tests)产出测试套件,用ts:install([{xcomp,"<FILE>"}])交叉编译测试用例,将$ERL_TOP/release/tests整体拷贝到目标机,最后在目标机运行:
$ erl -s ts install -s ts run all_tests -s init stop十、总结:四段式流程与常见坑位
| 阶段 | 关键产物 | 核心命令 |
|---|---|---|
| 1. 工具链 | /Volumes/xtools/${CT_TARGET}下的交叉编译器 | ct-ng build(配合 list-steps 断点续跑) |
| 2. 依赖 | /Volumes/xtools-build-env/local下的 zlib/openssl/ncurses | 各库的 configure + make install |
| 3. sysroot | /Volumes/xtools-build-env/sysroot | armv8-rpi3-linux-gnueabihf-populate -s ... -d ... |
| 4. OTP | /Volumes/xtools-build-env/otp_22.0发布目录 | otp_build configure/boot/release |
本文中的命令与配置均以仓库当前文档 HOWTO/INSTALL-RASPBERRYPI3.md 为准。实操中最大的不确定性来自版本组合:工具链组件版本必须对齐目标系统(glibc、binutils、内核头文件),第三方库版本要随 OTP 发布周期调整(例如 OpenSSL 1.1.1b、zlib 1.2.13 均是当时测试的版本),并注意 macOS 特有的坑(大小写敏感磁盘映像、GNU sed 替换、populate 脚本权限)。只要守住"工具链 → 依赖 → sysroot → OTP"这条主线,每个阶段的产物都可以独立验证,遇到问题也就能准确定位到具体环节。
- 编程语言
- 语言运行时
- 标准库
- 编译器
- 并发编程
【免费下载链接】otp
Erlang/OTP
相关推荐
Erlang/OTP 交叉编译完全指南:基于 otp_build 与 configure 的跨平台构建实战
Erlang/OTP 交叉编译完全指南:基于 otp_build 与 configure 的跨平台构建实战 本篇指南以 Erlang/OTP 官方交叉编译文档(
编程语言语言运行时标准库编译器并发编程Metabase 用户预置(User Provisioning with SCIM)配置指南:SCIM 2.0 端点、Token 管理与 Okta/Entra ID 集成
Metabase 用户预置(User Provisioning with SCIM)配置指南:SCIM 2.0 端点、Token 管理与 Okta/Entra
编程语言语言运行时标准库编译器并发编程在树莓派上构建 Apache MXNet:原生编译与交叉编译全流程指南
在树莓派上构建 Apache MXNet:原生编译与交叉编译全流程指南 MXNet 官方支持在 ARM 设备(如树莓派)上运行,本文基于仓库中的 树莓派构建指南
深度学习人工智能机器学习分布式训练
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考