blog.mahoroi.com

RustのRaspberry Pi向けクロスコンパイル、OpenSSLの対策

-

Rust 入門の第一歩として簡単な HTTP リクエストを実行するコードを書きました。このコードは開発環境である Ubuntu18 (x86_64) 上では問題なく動作したのですが、Raspberry Pi Model 3 (armv7l) 用にクロスコンパイルしようとすると OpenSSL 周りでエラーが起きてビルドできませんでした。

今回はこの OpenSSL 問題の対処法をまとめます。

openssl-sysでエラー

reqwest を使って自身の IP アドレスを取得するコードです。

src/main.rs
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),
    }
}
Cargo.toml
[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 を作成します。

.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-gnueabihfarmv7gcc をインストールし、armv7 の OpenSSL をビルドします。また rustup target add armv7-unknown-linux-gnueabihfarmv7 のターゲットを rustup に追加します。

Dockerfile
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/ipaddressarmv7 のバイナリが生成されます。

Raspberry Pi に持っていき、実行してみます。

pi@raspberrypi: $ ./ipaddress
{
    "ip": "0.0.0.0",
}

無事に動きました。Dockerfile 内で完結しているため、CI で手軽に armv7 用のクロスコンパイルもできるようになりました。

別法: OpenSSLの代わりにRustlsを使う

今回使用したは reqwest は OpenSSL の代わりに Rustls を使うことができます。Rustls を使うと OpenSSL のビルドが不要になり、より手軽にクロスコンパイルが可能になります。

Cargo.toml
[dependencies]
reqwest = { version = "0.10", default-features = false, features = ["blocking", "json", "rustls-tls"] }
tokio = { version = "0.2", features = ["full"] }

参考