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
    
    TARGET=x86_64-unknown-linux-musl nix develop .#$TARGET --command \
      cargo build --target $TARGET
    

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