David Alsh
David Alsh

Reputation: 7675

How to cross compile Rust across operating systems and CPU architectures?

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

Answers (3)

David Alsh
David Alsh

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

Peter Cordes
Peter Cordes

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

Caesar
Caesar

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

Related Questions