Cross-compilation in CI#

Scenario: you want to check every feature combination on several target triples, from a single CI job, without a matrix of GitHub Actions jobs.

Declare the targets#

Declare the target list in the workspace Cargo.toml:

# Workspace that checks every feature combination across multiple target triples.
[workspace]
resolver = "2"
members = ["crates/*"]

[workspace.package]
version = "0.1.0"
edition = "2024"
publish = false

[workspace.metadata.cargo-fc]
# Check every feature combination on each of these targets.
targets = [
  "x86_64-unknown-linux-gnu",
  "wasm32-unknown-unknown",
]
# `check` never links, so plain cargo cross-checks fine without a C toolchain.
# (Cross-compiling native-C deps would use the cargo-zigbuild default instead.)
driver = "cargo"

A single cargo fc check then visits every feature combination on every target:

$ cargo fc --summary-only check --workspace
 
     Checking [1/8] app ( target = x86_64-unknown-linux-gnu, features = [] )
     Checking [2/8] app ( target = x86_64-unknown-linux-gnu, features = [simd] )
     Checking [3/8] app ( target = x86_64-unknown-linux-gnu, features = [simd, std] )
     Checking [4/8] app ( target = x86_64-unknown-linux-gnu, features = [std] )
     Checking [5/8] app ( target = wasm32-unknown-unknown, features = [] )
     Checking [6/8] app ( target = wasm32-unknown-unknown, features = [simd] )
     Checking [7/8] app ( target = wasm32-unknown-unknown, features = [simd, std] )
     Checking [8/8] app ( target = wasm32-unknown-unknown, features = [std] )
 
    Finished 8 feature combinations for 1 package across 2 targets in 0.00s
 
        PASS app ( target = x86_64-unknown-linux-gnu, 0 errors, 0 warnings, features = [] )
        PASS app ( target = x86_64-unknown-linux-gnu, 0 errors, 0 warnings, features = [simd] )
        PASS app ( target = x86_64-unknown-linux-gnu, 0 errors, 0 warnings, features = [simd, std] )
        PASS app ( target = x86_64-unknown-linux-gnu, 0 errors, 0 warnings, features = [std] )
        PASS app ( target = wasm32-unknown-unknown, 0 errors, 0 warnings, features = [] )
        PASS app ( target = wasm32-unknown-unknown, 0 errors, 0 warnings, features = [simd] )
        PASS app ( target = wasm32-unknown-unknown, 0 errors, 0 warnings, features = [simd, std] )
        PASS app ( target = wasm32-unknown-unknown, 0 errors, 0 warnings, features = [std] )

(Add more triples — aarch64-unknown-linux-gnu, x86_64-pc-windows-msvc, … — to the targets list as needed.)

Keep host-executing commands host-only#

check and clippy only need each target’s rustc, so they cross-compile fine. test and run execute the binary and can’t run a foreign target — keep them host-only:

[workspace.metadata.cargo-fc.subcommands.test]
expand_targets = false

Lint everything in one invocation#

- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
  with:
    targets: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, wasm32-unknown-unknown
- uses: romnn/cargo-feature-combinations@main
- run: cargo fc clippy

A single cargo fc clippy iterates every configured target and feature combination.

Native-C dependencies#

If a target pulls in native-C build dependencies, cargo fc uses cargo-zigbuild automatically for non-host targets. Install zig and cargo-zigbuild on the runner, or override the driver.

Throughput#

Add --aggregate-targets to batch each combination’s targets into one Cargo invocation on many-core runners:

- run: cargo fc clippy --aggregate-targets

For fanning targets out across separate jobs instead, see Continuous integration.