crystal-chinaChina
  • 文档
  • Github
  • 注册
  • 登录
目录
  • 前言
  • 简介
  • 安装 ➤
    • 包管理
  • 写给 Rubyists ➤
    • 类型
    • 方法
    • 代码块
    • 杂项
    • 性能因素(WIP)
    • 迁移 Ruby 代码到 Crystal
  • 基础知识
  • 查找性能瓶颈 (WIP)
  • 交叉编译

交叉编译

最后编辑于: 2025年03月04日

Crystal 通过 --cross-compile 支持交叉编译,但是你常常需要也传递一个 --target TARGET 来告诉编译器,为哪一个目标平台,编译生成目标(target)文件。

例如:下面的命令生成了一个适用于苹果 OSX ARM64 操作系统的的目标文件 main.o 当命令执行成功之后,会打印一段命令来期望 在目标平台执行 来生成可执行文件。

   1  
   2  ╰──➤ $ crystal --cross-compile --target aarch64-darwin -o main
   3  cc start_server.o -o start_server  -rdynamic -L/home/zw963/.asdf/installs/crystal/1.15.0/bin/../lib/crystal -lxml2 -lgmp -lyaml -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lpcre2-8 -lgc -lpthread -ldl -liconv
   4  
   5  ╰──➤ $ file main.o
   6  main.o: Mach-O 64-bit arm64 object, flags:<|SUBSECTIONS_VIA_SYMBOLS>
   7  

完整的 target 列表可以参考 platform_support ,但常用的支持平台有如下几个:

target 描述 支持的版本 支持级别
aarch64-darwin M 系列(ARM)苹果电脑(Apple Silicon) 11+ Tier 1
x86_64-darwin Intel CPU 的苹果电脑 11+ Tier 1
x86_64-linux-gnu 64 位 Linux 内核 4.14+, GNU libc 2.26+ Tier 1
x86_64-linux-musl 基于 Alpine 的 64 位 Linux 内核 4.14+, MUSL libc 1.2+ Tier 1
aarch64-linux-gnu AARCH64 Linux GNU libc 2.26+ Tier 2
aarch64-linux-musl AARCH64 Linux MUSL libc 1.2+ Tier 2
x86_64-windows-msvc 64 位 Windows (MSVC) Win7+ Tier 3

备注

  • Tier 级别:1 为保证工作,2 期望工作,3 部分工作
  • 基于 musl 的 target 主要用来编译生成该平台静态 binary.
  • 随着 Windows 平台支持越来越完善,支持级别应该很快被提升。

§ 静态链接

Crystal 支持静态链接,当传递 --static 参数时,会尝试查找依赖库的静态版本来创建 可执行文件,以使得稍后运行时不再需要这些库,带来的优点是,随便拷贝到同 target 的 任意一个机器上就可以运行,缺点,就是文件大小会增加。

你需要安装所有依赖库的静态版本,否则编译器会报错,假设某个库名称叫做 foo, 通常 该库的静态版本叫做 foo-static.

和动态库一样,Crystal 使用 CRYSTAL_LIBRARY_PATH 环境变量,从前往后查找依赖库。

作为一个编译型语言,可以编译生成一个没有外部依赖的静态的 binary,对于部署到 另一台机器(生产环境)来说,相较于 Ruby 这种使用源码部署的动态语言,是极其便利的。

当前,静态编译一个 Linux 下的静态 binary, 需要 musl-libc 支持,常见的做法是 使用 Docker。

§ 使用基于Alpine Linux 的官方 Docker 镜像编译一个静态 binary

这里对如何使用 docker 不做过多的解释,参见下面的 Dockerfile,它做的事是将本地项目 目录挂载到 docker 容器中,在其中编译生成静态 binary,官方预发布的编译器也是这么做的。

Dockerfile
   1  
   2  ARG alpine_mirror=mirrors.ustc.edu.cn
   3  
   4  FROM crystallang/crystal:1-alpine AS official_release
   5  
   6  ARG alpine_mirror
   7  RUN sed -i "s/dl-cdn.alpinelinux.org/$alpine_mirror/g" /etc/apk/repositories
   8  
   9  # 在这里添加需要的静态版本依赖库(编译错误会告诉你)。
  10  RUN --mount=type=cache,target=/var/cache/apk \
  11      set -eux; \
  12      apk add \
  13      --update \
  14      sqlite-static \
  15      ;
  16  
  17  # 下面这一堆,只是为了使用 fixid 处理挂载目录到容器内新生成的文件的权限问题。
  18  RUN addgroup -g 1000 docker && \
  19      adduser -u 1000 -G docker -h /home/docker -s /bin/sh -D docker
  20  RUN wget -O - https://github.com/boxboat/fixuid/releases/latest | \
  21      grep -E -o "boxboat/fixuid/releases/tag/[^\"]*\" data-view-component" | \
  22      cut -d'"' -f1 | rev|cut -d'/' -f1 |rev |sed 's#^v##' > fixuid_version
  23  RUN wget https://github.com/boxboat/fixuid/releases/download/v$(cat fixuid_version)/fixuid-$(cat fixuid_version)-linux-amd64.tar.gz -O - | tar zxvf - -C /usr/local/bin
  24  RUN USER=docker && \
  25      GROUP=docker && \
  26      chown root:root /usr/local/bin/fixuid && \
  27      chmod 4755 /usr/local/bin/fixuid && \
  28      mkdir -p /etc/fixuid && \
  29      printf "user: $USER\ngroup: $GROUP\n" > /etc/fixuid/config.yml
  30  USER docker:docker
  31  
  32  WORKDIR /app
  33  
  34  ENTRYPOINT ["fixuid"]
  35  
  36  CMD ["shards", "build", \
  37       "-Dstrict_multi_assign", "-Dno_number_autocast", \
  38       "-Dpreview_overload_order", "-Duse_pcre2", \
  39       "--link-flags=-Wl,-L/app", "--link-flags=-s", \
  40       "--progress", "--static", "--stats", "--time", "--no-debug", \
  41       "--production", "--release"]
  42  

进入项目目录,将上面的文件保存为 Dockerfile,使用下面的命令构建 Docker 镜像:

   1  
   2  docker build -t build_crystal_amd64_static_binary .
   3  

然后运行这个镜像,来创建静态的 binary

   1  
   2   ╰──➤ $ docker run -it -u $(id -u):$(id -g) -v $PWD:/app build_crystal_amd64_static_binary
   3  fixuid: fixuid should only ever be used on development systems. DO NOT USE IN PRODUCTION
   4  fixuid: runtime UID '1000' already matches container user 'docker' UID
   5  fixuid: runtime GID '1000' already matches container group 'docker' GID
   6  Dependencies are satisfied
   7  Building: myip
   8  Parse:                             00:00:00.000115461 (   1.26MB)
   9  Semantic (top level):              00:00:00.327796720 ( 134.21MB)
  10  Semantic (new):                    00:00:00.001296853 ( 134.21MB)
  11  Semantic (type declarations):      00:00:00.028085586 ( 134.21MB)
  12  Semantic (abstract def check):     00:00:00.008466714 ( 142.21MB)
  13  Semantic (restrictions augmenter): 00:00:00.010456306 ( 142.21MB)
  14  Semantic (ivars initializers):     00:00:00.009350086 ( 150.21MB)
  15  Semantic (cvars initializers):     00:00:00.158940664 ( 174.28MB)
  16  Semantic (main):                   00:00:00.377006249 ( 246.40MB)
  17  Semantic (cleanup):                00:00:00.000630417 ( 246.40MB)
  18  Semantic (recursive struct check): 00:00:00.000639605 ( 246.40MB)
  19  Codegen (crystal):                 00:00:00.315987432 ( 270.40MB)
  20  Codegen (bc+obj):                  00:00:14.472953394 ( 270.40MB)
  21  Codegen (linking):                 00:00:00.263343233 ( 270.40MB)
  22  
  23  Codegen (bc+obj):
  24   - no previous .o files were reused
  25  
  26  

如果你还希望 build 其他 target 的 Linux 静态 binary, 例如:aarch64-linux-musl 你需要采用 Docker 的 multi-stage 功能,在和当前 Linux 相同的 stage 生成 .o 中间 文件,然后在目标的 stage 执行 linking,会更加复杂一些,这里不做赘述。

因为我们有更好的方案,使用 zig cc, 甚至无需 Docker 容器,也可以在 Linux/OSX 下 使用交叉编译来生成下面平台的静态可执行文件!

  • aarch64-darwin
  • x86_64-darwin
  • x86_64-linux-musl
  • aarch64-linux-musl

§ 使用 zig cc 来实现交叉编译

详细的信息见我写的 这篇英文文章, 这里也同样不赘述,仅提供步骤:

§ 1. 确保本地正确安装 zig

   1  
   2  sudo pacman -S zig
   3  

§ 2. 从 Github 克隆项目

   1  
   2  https://github.com/crystal-china/magic-haversack
   3  

§ 3. 下载必须的静态版本依赖库

如果你希望自己手动从 alpinelinux CDN 以及 Homebrew Formula 安装所需的 依赖库的静态版本,你需要本地配置正确的 Ruby 环境,并运行如下命令:‘

   1  
   2  bundle install && rake fetch:all
   3  

如果一切正常(可能需要梯子),你会看到新创建的 lib 文件夹下面会有类似下面的目录结构:

lib 目录结构
   1  
   2   ╰──➤ $ tree -L2 lib
   3  lib
   4  ├── aarch64-linux-musl
   5  │   ├── libcrypto.a
   6  │   ├── libevent.a
   7  │   ├── libevent_pthreads.a
   8  │   ├── libgc.a
   9  │   ├── libgmp.a
  10  │   ├── libicudata.a
  11  │   ├── libicuuc.a
  12  │   ├── liblzma.a
  13  │   ├── libpcre2-8.a
  14  │   ├── libsodium.a
  15  │   ├── libsqlite3.a
  16  │   ├── libssl.a
  17  │   ├── libxml2.a
  18  │   ├── libyaml.a
  19  │   ├── libz.a
  20  │   └── pkgconfig
  21  ├── aarch64-monterey
  22  │   ├── libcrypto.a
  23  │   ├── libevent.a
  24  │   ├── libevent_pthreads.a
  25  │   ├── libgc.a
  26  │   ├── libgmp.a
  27  │   ├── libiconv.a
  28  │   ├── liblzma.a
  29  │   ├── libpcre2-8.a
  30  │   ├── libsodium.a
  31  │   ├── libsqlite3.a
  32  │   ├── libssl.a
  33  │   ├── libxml2.2.dylib
  34  │   ├── libxml2.dylib
  35  │   ├── libyaml.a
  36  │   ├── libz.a
  37  │   └── pkgconfig
  38  ├── x86_64-linux-musl
  39  │   ├── libcrypto.a
  40  │   ├── libevent.a
  41  │   ├── libevent_pthreads.a
  42  │   ├── libgc.a
  43  │   ├── libgmp.a
  44  │   ├── liblzma.a
  45  │   ├── libpcre2-8.a
  46  │   ├── libsodium.a
  47  │   ├── libsqlite3.a
  48  │   ├── libssl.a
  49  │   ├── libxml2.a
  50  │   ├── libyaml.a
  51  │   ├── libz.a
  52  │   └── pkgconfig
  53  └── x86_64-monterey
  54      ├── libcrypto.a
  55      ├── libevent.a
  56      ├── libevent_pthreads.a
  57      ├── libgc.a
  58      ├── libgmp.a
  59      ├── libiconv.a
  60      ├── liblzma.a
  61      ├── libpcre2-8.a
  62      ├── libsodium.a
  63      ├── libsqlite3.a
  64      ├── libssl.a
  65      ├── libxml2.2.dylib
  66      ├── libxml2.dylib
  67      ├── libyaml.a
  68      ├── libz.a
  69      └── pkgconfig
  70  
  71  9 directories, 58 files
  72  
💡 小提示

另一个更好的选择是,你无需配置 Ruby ,也无需搭梯子,直接从 项目 release 页面 下载 libs.tar.gz 并解压缩到项目根目录即可。

§ 4. 将 bin/ 加入 $PATH

无论你当前使用什么 shell, 请确保 BASH 4.0+ 可用。

脚本中会使用到 sed, 这在 Linux 下没问题,OSX 下,请确保 GNU 工具链命令行下优先被使用。

确保设置完成后,sb 和 sb_static 命令均可以执行被执行。

   1  
   2   ╰──➤ $ which sb sb_static
   3  /home/zw963/Crystal/bin/sb
   4  /home/zw963/Crystal/bin/sb_static
   5  

§ 5. 最后一步,确保你的项目使用 shards 包管理工具来编译并运行。

对于大部分项目来说,这应该都不是问题。

进入你的项目后,执行下面的命令来构建针对不同的 target 的 binary。


x86_64 Linux 下运行的静态可执行文件

   1  
   2  ╰─ $ sb --cross-compile --target=x86_64-linux-musl
   3  zig cc -target x86_64-linux-musl bin/college.o -o bin/college  -rdynamic -static -L/home/zw963/Crystal/crystal-china/magic-haversack/lib/x86_64-linux-musl -lgmp -lyaml -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lpcre2-8 -lgc -lpthread -ldl -levent -lunwind
   4  
   5   ╰─ $ file bin/college
   6  bin/college: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), static-pie linked, with debug_info, not stripped
   7  

我还提供了一个脚本叫做 sb_static, 它可以帮你运行上面的命令,你自己只需要传递其他 需要的选项即可,例如:

   1  
   2  sb_static --production --no-debug --link-flags=-s
   3  
💡 小提示

本站在部署时,正是使用上面的脚本构建,生成一个静态可执行文件,然后拷贝这一个文件 到远程服务器即可,非常简单!

为了避免正在运行的网站可执行文件无法直接被覆盖,需要停止服务才能覆盖的问题,并且 考虑节省流量,同时拷贝速度快一些,本站使用了 二进制 diff/patch 工具, 在开发机本地针对上次部署的二进制文件生成 patch 文件, 然后仅拷贝 patch 文件到远程服务器, 最后在远程服务器应用 patch 文件,最后重启服务即可生效,全程无需停止服务。

本站生成的部署文件大概 17M 左右, 其中包含了大约 3M 的 assets(图片,JS, CSS 等)文件, 这些网站运行时必须的文件,在运行时会被自动挂载到文件系统中。


AARCH64 linux 下运行的静态可执行文件

   1  
   2  ╰─ $ sb --cross-compile --target=aarch64-linux-musl
   3  zig cc -target aarch64-linux-musl bin/college.o -o bin/college  -rdynamic -static -L/home/zw963/Crystal/crystal-china/magic-haversack/lib/aarch64-linux-musl -lgmp -lyaml -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lpcre2-8 -lgc -lpthread -ldl -levent -lunwind
   4  
   5   ╰─ $ file bin/college
   6  bin/college: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), static-pie linked, with debug_info, not stripped
   7  

Intel 处理器的苹果电脑可执行文件

   1  
   2  ╰─ $ sb --cross-compile --target=x86_64-darwin
   3  zig cc -target x86_64-macos-none bin/college.o -o bin/college  -rdynamic -static -L/home/zw963/Crystal/crystal-china/magic-haversack/lib/x86_64-monterey -lgmp -lyaml -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lpcre2-8 -lgc -lpthread -ldl -levent -liconv -lunwind
   4  
   5    ╰─ $ file bin/college
   6  bin/college: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|NO_REEXPORTED_DYLIBS|PIE|HAS_TLV_DESCRIPTORS>
   7  

ARM64 处理器的苹果电脑可执行文件

   1  
   2  
   3  ╰─ $ sb --cross-compile --target=aarch64-darwin
   4  zig cc -target aarch64-macos-none bin/college.o -o bin/college  -rdynamic -static -L/home/zw963/Crystal/crystal-china/magic-haversack/lib/aarch64-monterey -lgmp -lyaml -lz `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libssl || printf %s '-lssl -lcrypto'` `command -v pkg-config > /dev/null && pkg-config --libs --silence-errors libcrypto || printf %s '-lcrypto'` -lpcre2-8 -lgc -lpthread -ldl -levent -liconv -lunwind
   5  
   6  ╰─ $ file bin/college
   7  bin/college: Mach-O 64-bit arm64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|NO_REEXPORTED_DYLIBS|PIE|HAS_TLV_DESCRIPTORS>
   8  

访问 magic-haversack 项目主页的 REAME 以及 doc 了解更多的信息。

previous_page查找性能瓶颈 (WIP)

交叉编译

next_page没有下一页了
欢迎在评论区留下你的见解、问题或建议

正在预览...
正在读取评论...
Crystal China
admin@crystal-china.org
githubtwittercrystal-lang