Wednesday, September 23, 2020

Cross-compiling Rust To Linux On Mac

In my last blog post I said I wanted to spend some time learning new things. The first of those is Rust. I had previously tried learning it, but got distracted before I got very far.

Since one of the things I'd use Rust for is web pages, I decided to learn how to compile to WebAssembly, how to interface with Javascript, and how to use WebSockets. At home, I use a Mac to work on my web projects, so for Rust I am compiling a native server and a wasm client. But I also wanted to try running this on redblobgames.com, which is a Linux server. How should I compile to Linux? My first thought was to use my Linux machine at home. I can install the Rust compiler there and compile the server on that machine. Alternatively, I could use a virtual machine running Linux. Both of these options seemed slightly annoying.

I've been curious how much work it would take to cross-compile, and I found this great post from Tim Ryan. My setup is simpler than his, so I didn't need everything he did. I started with these commands from his blog post:

rustup target add x86_64-unknown-linux-musl brew install FiloSottile/musl-cross/musl-cross mkdir -p .cargo cat >>.cargo/config <<EOF [target.x86_64-unknown-linux-musl] linker = "x86_64-linux-musl-gcc" EOF 

I then compiled for Linux:

TARGET_CC=x86_64-linux-musl-gcc cargo build --release --target=x86_64-unknown-linux-musl 

Unfortunately this failed with an error about OpenSSL. Tim's post has a solution to this. Before implementing that complicated solution I realized that I should't need SSL/TLS anyway. My server talks regular websockets, not secure websockets, and then I use nginx to proxy them into secure websockets. So I disabled the secure websockets with this in Cargo.toml, the file that has the Rust project configuration:

[target.'cfg(target_arch = "x86_64")'.dependencies] tungstenite = { version = "0.9", default-features = false, features = [] } 

At first I tried features = [] but that wasn't good enough. I needed to also use default-features = false to disable the TLS. With this, the binary built, and I was able to run it on Linux!

So now I have a Makefile that builds the wasm client, the Mac server for local testing, and the Linux server for production. Fun!

BUILD = build  RS_SRC = $(shell find src -type f -name '*.rs') Cargo.toml WASM = target/wasm32-unknown-unknown/debug/rust_chat_server.wasm  run-server: target/debug/chat_server  # local testing server  RUST_BACKTRACE=1 cargo run --bin chat_server  target/debug/chat_server: $(RS_SRC)  # production server  cargo build --bin chat_server  target/x86_64-unknown-linux-musl/release/chat_server: $(RS_SRC)  TARGET_CC=x86_64-linux-musl-gcc cargo build \      --release --target=x86_64-unknown-linux-musl  $(WASM): $(RS_SRC)  cargo build --lib --target wasm32-unknown-unknown  $(BUILD)/rust_chat_server_bg.wasm: $(WASM) index.html  wasm-bindgen --target no-modules $< --out-dir $(BUILD)  mkdir -p $(BUILD)  cp index.html $(BUILD)/ 

My Cargo.toml file is kind of terrible but it works so far for building the three outputs:

[package] name = "rust_chat_server" version = "0.1.0" authors = ["Amit Patel <redblobgames@gmail.com>"] edition = "2018"  [lib.'cfg(target_arch = "wasm32")'] crate-type = ["cdylib"]  [[bin]] name = "chat_server" path = "src/chat_server.rs"  [dependencies] wasm-bindgen = "0.2" serde = { version = "1.0", features = ["derive"] } bincode = "1.2"  [target.'cfg(target_arch = "x86_64")'.dependencies] tungstenite = { version = "0.9", default-features = false, features = [] } 

That's it for now. I'm not a big fan of writing client-server code in large part because I want my pages to still work in thirty years, and that's best if there's no server component. But I want to spend time this year learning things for myself rather than trying to produce useful tutorials, so I'm going to explore this.

Tim's blog post was a huge help. Without it, I would've compiled the server on Linux. Thanks Tim!

I've placed it on github.

No comments: