cargo-zigbuildで簡単にクロスコンパイルする / マルチアーキテクチャ対応のイメージ作成に利用する

C依存のあるプロジェクトのビルドが難しい

Rustはビルトインでクロスコンパイルをサポートしている。以下のようにすることでクロスコンパイル可能となっている。

rustup target add aarch64-unknown-linux-musl
cargo build --target aarch64-unknown-linux-musl

しかし、これではビルドできない場合がある。依存するcrateのビルド時、C言語への依存があった場合はC言語のビルドに必要なツールチェイン(コンパイラとリンカ)を揃える必要がある。

warning: cc: error: unrecognized command-line option `-arch`
error: failed to run custom build command for `wasmtime-fiber`
Caused by:
  ...
  running "cc" ...

zig cc を利用する

従来は clangllvm-ar を追加でインストールする必要があったが、最近話題になっているzigを使うことでこれを回避することができる。

Zig Makes Rust Cross-compilation Just Work · Um, actually...

具体的には、次のようなwrapperを用意し、

#!/bin/sh
/path-to-zig/zig cc -target aarch64-linux $@

以下のようにリンカ/ビルド時に利用する。

[target.aarch64-unknown-linux-musl]
linker = "/path-to-wrapper/zcc"
CC="/path-to-wrapper/zcc" cargo build --target aarch64-unknown-linux-musl

cargo-zigbuild

これをcargo subcommandとして簡単に利用できるようになった cargo-zigbuild を紹介する。

GitHub - messense/cargo-zigbuild: Compile Cargo project with zig as linker

使い方としてはこんな感じ。かなり簡単でとても嬉しい。

rustup target add aarch64-unknown-linux-musl
cargo zigbuild --release --target aarch64-unknown-linux-musl

インストールも簡単で、pipが入っていれば pip install cargo-zigbuild を打つだけでzigも一緒にインストールしてくれる。

マルチアーキテクチャ対応のイメージ作成に利用する

前回、Rust + GitHub Actionsでマルチアーキテクチャ対応のイメージをなるべく早く作るという記事を書いた。この時まさにマルチアーキテクチャ対応のイメージビルドで困っていて、イメージビルド時に clangllvm をインストールしていたんだけど、cargo-zigbuildを利用することでこの箇所が簡単にできる。

FROM --platform=$BUILDPLATFORM rust:1.63 as builder

# clang や llvm が必要なくなった! cargo-zigbuild をインストールする
RUN apt update -y && apt install python3-pip -y && pip3 install cargo-zigbuild

RUN cargo new --bin app
WORKDIR /app

COPY ./Cargo.lock ./Cargo.lock
COPY ./Cargo.toml ./Cargo.toml

# リンカの指定も必要なくなった!
# ENV CC_aarch64_unknown_linux_musl=clang
# ENV AR_aarch64_unknown_linux_musl=llvm-ar
# ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-Clink-self-contained=yes -Clinker=rust-lld"
# 
# ENV CC_x86_64_unknown_linux_musl=clang
# ENV AR_x86_64_unknown_linux_musl=llvm-ar
# ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-Clink-self-contained=yes -Clinker=rust-lld"

ARG TARGETPLATFORM
RUN case "$TARGETPLATFORM" in \
  "linux/arm64") echo aarch64-unknown-linux-musl > /rust_target.txt ;; \
  "linux/amd64") echo x86_64-unknown-linux-musl > /rust_target.txt ;; \
  *) exit 1 ;; \
esac
RUN rustup target add $(cat /rust_target.txt)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
    --mount=type=cache,target=/app/target \
    cargo zigbuild --release --target $(cat /rust_target.txt)
RUN rm src/*.rs

COPY ./src ./src
RUN touch ./src/main.rs
RUN --mount=type=cache,target=/usr/local/cargo/registry \
    --mount=type=cache,target=/app/target \
    # cargo install が使えないので、代わりに手動でコピーする
    cargo zigbuild --release --target $(cat /rust_target.txt) && \
    cp target/$(cat /rust_target.txt)/release/minecraft-command-bot /usr/local/bin/app

FROM alpine
COPY --from=builder /usr/local/bin/app .
CMD ["./app"]

かなり嬉しい。