Reputation: 7675
I am learning Rust and writing some basic CLI tools as an exercise. I am storing my application source in Github, using Github actions to generate binaries and publish those binaries via Github releases.
The issue is; I am unsure how to cross compile my Rust application for various target architectures and operating systems.
(Apologise for the comparison) Previously when using Go, I could specify the target CPU architecture and target Operating System in the build command like:
env GOARCH=arm64 GOOS=darwin go build
When looking to see if there is an equivalent in Rust I am seeing instructions telling me to use virtualization and various other techniques to cross compile.
I suspect I might just be bad at researching, is there an equivalent simple way to cross compile Rust applications?
If not, why is that and could you point me to resources to help me learn how to do it?
Upvotes: 3
Views: 1866
Reputation: 7675
Sorry to answer my own question. The answers by the other posters were also valuable in different ways.
Turns out, for the purposes of automating the creation of binaries from a CI/CD pipeline, the best choice is not to cross compile.
MacOS and Windows have shared dynamic libraries that need to be used during compilation and you cannot use them from a Linux host (I think for licensing reasons?).
The best option for me was to use the target platform to compile the target binary.
Lukily, Github actions offers free Linux, Windows, and MacOS runners which you can use to build for the desired targets.
Apologies, I don't mean this as self promotion but I created a template starter project that produces binaries and a GitHub release via Github actions for:
win_amd64
macos_amd64
linux_amd64
win_arm64
macos_arm64
linux_arm64
https://github.com/alshdavid-templates/rust-cross-platform-release
I'm sure there are better ways, but hope that's helpful!
Upvotes: 2
Reputation: 365527
For one-off testing, or playing around on https://godbolt.org/, you can use --target=
, similar to clang -target
.
For example, Godbolt with -O --target=aarch64-apple-darwin
instead of the default x86_64-unknown-linux-gnu
. (I guess x86_64 has a 4th component, like kind of x86, which can be pc
for Windows, unknown
for non-Windows.)
Godbolt's "compile to binary" option in the "output" dropdown gives no output, probably because it's still using x86-64 objdump
instead of llvm-objdump
which can auto-detect architecture of object files.
This even works with use std::arch::asm;
if you want to test out how inline asm for different architectures compiles.
To see what targets are available, --print target-list
. (You can do this on Godbolt, just look at the compiler output pane.)
$ rustc --print target-list
aarch64-apple-darwin
aarch64-apple-ios
...
riscv64gc-unknown-linux-gnu
riscv64gc-unknown-linux-musl
...
x86_64-pc-windows-gnu
x86_64-pc-windows-gnullvm
x86_64-pc-windows-msvc
...
x86_64-unknown-linux-gnu
...
x86_64h-apple-darwin
Upvotes: 0
Reputation: 8544
cross
makes this really easy, especially since it's supported by actions-rs/cargo
.
I'm using something like
name: 'Release'
on:
push:
tags:
- 'v*'
env:
CARGO_INCREMENTAL: 0
jobs:
build:
name: Binary
strategy:
fail-fast: false
matrix:
job:
- { target: x86_64-unknown-linux-musl, exe: amd64-linux, os: ubuntu-latest }
- { target: aarch64-unknown-linux-musl, exe: aarch64-linux, os: ubuntu-latest }
- { target: armv7-unknown-linux-musleabi, exe: armv7-linux, os: ubuntu-latest }
- { target: wasm32-wasi, exe: wasi.wasm, os: ubuntu-latest }
- { target: x86_64-apple-darwin, exe: macos, os: macos-latest }
- { target: x86_64-pc-windows-msvc, exe: windows.exe, os: windows-2019 }
runs-on: ${{ matrix.job.os }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.62.0
override: true
target: ${{ matrix.job.target }}
components: rust-src # necessary for wasi, because there isn't a cross image for it
- uses: actions-rs/cargo@v1
with:
use-cross: true
args: --release --target=${{ matrix.job.target }} --locked
command: build
- name: Rename result
run: |
rm target/${{ matrix.job.target }}/release/name-of-binary.d
cp target/${{ matrix.job.target }}/release/name-of-binary* name-of-binary-${{ matrix.job.exe }}
- name: Archive production artifacts
uses: actions/upload-artifact@v2
with:
name: arty
path: name-of-binary-${{ matrix.job.exe }}
# Release artifacts in a separate step to make sure all are successfully produced and no partial release is created
on one of my projects. ([Edit:] Improved version with autopublishing)
I also specify
[profile.release]
lto = "fat"
strip = "debuginfo"
in my Cargo.toml
to make the released files a bit nicer.
It is probably worth noting that Rust crates make it much easier to build and link in C/C++ libraries than Go, possibly invoking CMake or worse. Cross-compiling such crates can be a lot more difficult and how to do it exactly is up to the specific crate.
Upvotes: 5