RustのRaspberry Pi向けクロスコンパイル、OpenSSLの対策
-
Rust 入門の第一歩として簡単な HTTP リクエストを実行するコードを書きました。このコードは開発環境である Ubuntu18 (x86_64
) 上では問題なく動作したのですが、Raspberry Pi Model 3 (armv7l
) 用にクロスコンパイルしようとすると OpenSSL 周りでエラーが起きてビルドできませんでした。
今回はこの OpenSSL 問題の対処法をまとめます。
openssl-sysでエラー
reqwest
を使って自身の IP アドレスを取得するコードです。
use std::collections::HashMap;
extern crate reqwest;
fn main() -> Result<(), reqwest::Error> {
let resp = reqwest::blocking::get("https://api.ipify.org?format=json")?
.json::<HashMap<String, String>>();
match resp {
Ok(ip) => {
println!("{:#?}", ip);
Ok(())
}
Err(err) => Err(err),
}
}
[package]
name = "ipaddress"
[dependencies]
reqwest = { version = "0.10", features = ["blocking", "json"] }
tokio = { version = "0.2", features = ["full"] }
まずこれを Ubuntu 上で実行してみると、レスポンスとして IP アドレスが取得できます。
$ cargo run
Compiling ipaddress v0.1.0 (/home/user/ipaddress)
Finished dev [unoptimized + debuginfo] target(s) in 1.98s
Running `target/debug/ipaddress`
{
"ip": "0.0.0.0",
}
次に Ubuntu 上で armv7
用にクロスコンパイルを試してみます。
Cargoのconfigを作成
ターゲットを armv7
にするために .cargo/config
を作成します。
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
cargo build
を実行。
$ rustup target add armv7-unknown-linux-gnueabihf
$ cargo build --target armv7-unknown-linux-gnueabihf --release
error: failed to run custom build command for `openssl-sys v0.9.58`
Caused by:
process didn't exit successfully: `/home/user/ipaddress/target/release/build/openssl-sys-64861fc66cdf26d9/build-script-main` (exit code: 101)
--- stdout
cargo:rustc-cfg=const_fn
cargo:rerun-if-env-changed=ARMV7_UNKNOWN_LINUX_GNUEABIHF_OPENSSL_LIB_DIR
ARMV7_UNKNOWN_LINUX_GNUEABIHF_OPENSSL_LIB_DIR unset
cargo:rerun-if-env-changed=OPENSSL_LIB_DIR
OPENSSL_LIB_DIR unset
cargo:rerun-if-env-changed=ARMV7_UNKNOWN_LINUX_GNUEABIHF_OPENSSL_INCLUDE_DIR
ARMV7_UNKNOWN_LINUX_GNUEABIHF_OPENSSL_INCLUDE_DIR unset
cargo:rerun-if-env-changed=OPENSSL_INCLUDE_DIR
OPENSSL_INCLUDE_DIR unset
cargo:rerun-if-env-changed=ARMV7_UNKNOWN_LINUX_GNUEABIHF_OPENSSL_DIR
ARMV7_UNKNOWN_LINUX_GNUEABIHF_OPENSSL_DIR unset
cargo:rerun-if-env-changed=OPENSSL_DIR
OPENSSL_DIR unset
run pkg_config fail: "Cross compilation detected. Use PKG_CONFIG_ALLOW_CROSS=1 to override"
--- stderr
thread 'main' panicked at '
Could not find directory of OpenSSL installation, and this `-sys` crate cannot
proceed without this knowledge. If OpenSSL is installed and this crate had
trouble finding it, you can set the `OPENSSL_DIR` environment variable for the
compilation process.
Make sure you also have the development packages of openssl installed.
For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora.
If you're in a situation where you think the directory *should* be found
automatically, please open a bug at https://github.com/sfackler/rust-openssl
and include information about your system as well as this message.
$HOST = x86_64-unknown-linux-gnu
$TARGET = armv7-unknown-linux-gnueabihf
openssl-sys = 0.9.58
', /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/openssl-sys-0.9.58/build/find_normal.rs:157:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...
error: build failed
x86_64
の OpenSSL で armv7l
コンパイルしようとしているためにエラーが発生します。そこで armv7
用の OpenSSL をビルドすることで armv7
のクロスコンパイルを試みます。
armv7のOpenSSLでクロスコンパイル
Docker を使います。apt install gcc-arm-linux-gnueabihf
で armv7
の gcc
をインストールし、armv7
の OpenSSL をビルドします。また rustup target add armv7-unknown-linux-gnueabihf
で armv7
のターゲットを rustup に追加します。
FROM rust:1.44
WORKDIR /app
ENV OPENSSL_VERSION=1.1.1f
ENV OPENSSL_LIB_DIR=/tmp/openssl-${OPENSSL_VERSION}
ENV OPENSSL_INCLUDE_DIR=/tmp/openssl-${OPENSSL_VERSION}/include
ENV MACHINE=armv7
ENV ARCH=arm
ENV CC=arm-linux-gnueabihf-gcc
RUN \
apt-get update && \
apt-get install -y gcc-arm-linux-gnueabihf
RUN \
cd /tmp && \
wget https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz && \
tar xzf openssl-${OPENSSL_VERSION}.tar.gz && cd openssl-${OPENSSL_VERSION} && \
./config shared && \
make
RUN \
rustup target add armv7-unknown-linux-gnueabihf
CMD ["cargo", "build", "--target", "armv7-unknown-linux-gnueabihf", "--release"]
以上でクロスコンパイルの準備が整いました。
クロスコンパイルする
最後に Docker コマンドを入力してクロスコンパイルします。
# build image
$ docker build -t cross-for-pi .
# cross compile
$ docker run -it -v "$PWD":/app cross-for-pi
コンパイルが成功すると、./target/armv7-unknown-linux-gnueabihf/release/ipaddress
に armv7
のバイナリが生成されます。
Raspberry Pi に持っていき、実行してみます。
pi@raspberrypi: $ ./ipaddress
{
"ip": "0.0.0.0",
}
無事に動きました。Dockerfile 内で完結しているため、CI で手軽に armv7
用のクロスコンパイルもできるようになりました。
別法: OpenSSLの代わりにRustlsを使う
今回使用したは reqwest
は OpenSSL の代わりに Rustls を使うことができます。Rustls を使うと OpenSSL のビルドが不要になり、より手軽にクロスコンパイルが可能になります。
[dependencies]
reqwest = { version = "0.10", default-features = false, features = ["blocking", "json", "rustls-tls"] }
tokio = { version = "0.2", features = ["full"] }