Rust Cross-compilation Tips

Setting Up a Basic Cross-Platform Rust Build Environment with Nix Flake

  1. Add a flake.nix file and run git add flake.nix:

    {
      inputs = {
        utils.url = "github:numtide/flake-utils";
        nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
      };
    
      outputs = {
        nixpkgs,
        utils,
        ...
      }:
        utils.lib.eachDefaultSystem (system: let
          pkgs = import nixpkgs {inherit system;};
          lib = pkgs.lib;
        in {
          devShells =
            {
              default = pkgs.mkShell {
                nativeBuildInputs = with pkgs; [
                  pkg-config
                  gcc
                ];
              };
            }
            // builtins.listToAttrs (map (target: {
              name = target;
              value = let
                cross = import nixpkgs {
                  inherit system;
                  crossSystem = {config = target;};
                };
                cpkgs = cross.pkgsMusl;
              in
                cross.mkShell {
                  nativeBuildInputs = with pkgs; [
                    pkg-config
                    gcc # Required for compiling proc macros and other dependencies
                  ];
                  env = let
                    normalized = lib.strings.toUpper (builtins.replaceStrings ["-"] ["_"] target);
                  in
                    with cpkgs; {
                      # https://doc.rust-lang.org/cargo/reference/environment-variables.html#configuration-environment-variables
                      "CARGO_TARGET_${normalized}_LINKER" = "${stdenv.cc.targetPrefix}cc";
                    };
                };
            }) ["x86_64-unknown-linux-musl" "aarch64-unknown-linux-musl"]);
        });
    }
    
  2. Build the project:

    # Local build
    nix develop .#default --command \
      cargo build
    
    # Cross-platform build
    #
    # Ensure the required Rust targets are installed:
    # rustup target add aarch64-unknown-linux-musl
    # rustup target add x86_64-unknown-linux-musl
    
    nix develop .#x86_64-unknown-linux-musl --command \
      cargo build --target x86_64-unknown-linux-musl
    

Handling C/C++ Dependencies (e.g., OpenSSL, PQ)

Use static linking and vendor-based bindings (which package C/C++ source code) to ensure stable and reproducible builds.

  1. Add target-specific dependencies in Cargo.toml:

    [target.x86_64-unknown-linux-musl.dependencies]
    openssl-sys = { version = "0.9", features = ["vendored"] }
    pq-sys = { version = "0.7", features = ["bundled"] }
    
    [target.aarch64-unknown-linux-musl.dependencies]
    openssl-sys = { version = "0.9", features = ["vendored"] }
    pq-sys = { version = "0.7", features = ["bundled"] }
    
  2. Build the project:

    # Static linking may require setting RUSTFLAGS.
    # However, this setting may not work for all architectures.
    RUSTFLAGS='-C target-feature=+crt-static' cargo build
    
    # x86_64-unknown-linux-musl enables crt-static by default, so no extra flags are needed.
    TARGET=x86_64-unknown-linux-musl nix develop .#$TARGET --command \
      cargo build --target $TARGET
    

CRT stands for the C runtime. The default crt-static setting varies by target. For example, x86_64-unknown-linux-musl enables it by default, whereas arm-unknown-linux-musleabi does not. 1

Setup Neovim in NixOS

Recently, I've been working on a large project in Rust, but my laptop isn't powerful enough to handle it efficiently. So, I decided to buy a new mini-PC specifically for this task.

mini PC is put on the left of my phone rack

Since I’m still more accustomed to developing on macOS and didn’t want to go through the hassle of setting up a Hackintosh, I decided to install NixOS on my new mini PC. Initially, I planned to continue using VSCode for remote development, but later realized that Neovim is also a great option.

Read more  ↩︎