Building a rust package derivation using `buildRustPackage` for `wasm32-unknown-unknown`

Hi folks! I’m trying to build a rust package derivation (defined with buildRustPackage) for wasm32-unknown-unknown.

From what I’ve read in the nix.dev docs and nixpkgs manual, it seems like rather than using pkgs.callPackage, instead I should just use pkgs.pkgsCross.wasm32-unknown-none.callPackage.

This leads to a couple issues:

  1. It says “package is not available on hostPlatform”. This remains even if I explicitly set the meta.platforms field to [ "wasm32-unknown-none" ] - it seems to be ignored. So, I build with the --impure flag and NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1.

  2. When the build starts, it starts building LLVM from scratch… which after an hour or so finished on my macbook air, but now it’s running 52000 tests in the checkPhase with no indication how long it will take :melting_face:

Have I taken a wrong path here?

I know that the cargo+rustc combo shipped for aarch64-darwin on nixos-unstable already supports wasm builds out of the box, as I can enter a shell with those tools and build for wasm easily with the --target wasm32-unknown-unknown cargo flag.

Maybe I’m doing something wrong?

Any advice appreciated!

OK llvm eventually finished building and after dumping all of the info on nix cross compilation I could find on the internet, I managed to get the derivation building for wasm successfully!

I got claude to summarise everything I learned, hopefully useful to someone in the future:


Here’s a tutorial on building Rust packages for WebAssembly using Nix:

Cross-Compiling Rust to WebAssembly with Nix

This guide explains how to set up a Nix build for compiling Rust packages to WebAssembly (specifically targeting wasm32-unknown-unknown).

Basic Setup

First, create a basic derivation for your WASM package:

{
  cargoLock,
  rustPlatform,
  buildPackages,
  lld,
}:
let
  target = "wasm32-unknown-unknown";
in
rustPlatform.buildRustPackage rec {
  pname = "my-wasm-lib";
  version = "0.1.0";
  src = ./.;  # Or your source location
  inherit cargoLock;

  # Disable tests since they can't run on WASM
  doCheck = false;
  
  # Required dependencies for WASM builds
  depsBuildBuild = [ 
    buildPackages.stdenv.cc 
    lld  # Needed for wasm-ld
  ];

  # Configure rustc for WASM
  env = {
    RUSTFLAGS = ''--cfg=web_sys_unstable_apis -C linker=wasm-ld'';
  };

  # Copy the WASM file to the output
  installPhase = ''
    mkdir -p $out/lib
    cp target/${target}/release/${pname}.wasm $out/lib/
  '';

  meta = {
    platforms = [ "wasm32-unknown-none" ];
  };
}

Adding to Your Flake

In your flake.nix, you’ll want to configure the cross-compilation target:

{
  description = "My WASM Package";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }: {
    packages.x86_64-linux.wasm-build = 
      nixpkgs.legacyPackages.x86_64-linux.pkgsCross.wasm32-unknown-none.callPackage 
        ./package.nix { };
  };
}

Platform-Specific Dependencies

If building on Darwin (macOS), you may need additional dependencies:

  # Add to depsBuildBuild:
  depsBuildBuild = [ 
    buildPackages.stdenv.cc 
    lld
  ] ++ buildPackages.lib.optionals buildPackages.stdenv.isDarwin [
    buildPackages.darwin.libiconv
  ];

Custom Build Profile

If you need a custom build profile for WASM (e.g., for optimization settings), you can specify it:

  buildType = "wasm_release";  # Matches profile.[wasm_release] in Cargo.toml

Common Issues and Solutions

  1. Linker Issues: If you see errors about unknown linker arguments, make sure you have both lld in depsBuildBuild and the correct RUSTFLAGS variable set.

  2. Missing WASM Features: Some crates might need web-specific features enabled. Add them to RUSTFLAGS:

    RUSTFLAGS = "--cfg=web_sys_unstable_apis ...";
    
  3. Build Platform vs Target Platform: Remember that some dependencies might be needed for the build platform (where compilation happens) while others are for the target platform (WASM). Use depsBuildBuild for build platform dependencies.

Example: Complex Package with Dependencies

Here’s a more complete example for a package using wasm-bindgen:

{
  cargoLock,
  rustPlatform,
  buildPackages,
  lld,
}:
let
  target = "wasm32-unknown-unknown";
in
rustPlatform.buildRustPackage rec {
  pname = "complex-wasm-app";
  version = "0.1.0";
  src = ./.;
  inherit cargoLock;

  doCheck = false;

  depsBuildBuild = [ 
    buildPackages.stdenv.cc 
    lld
  ] ++ buildPackages.lib.optionals buildPackages.stdenv.isDarwin [
    buildPackages.darwin.libiconv
  ];

  buildType = "wasm_release";

  env = {
    RUSTFLAGS = ''--cfg=web_sys_unstable_apis -C linker=wasm-ld'';
  };

  installPhase = ''
    mkdir -p $out/lib
    cp target/${target}/${buildType}/*.wasm $out/lib/
  '';

  meta = {
    description = "A complex WASM application";
    platforms = [ "wasm32-unknown-none" ];
  };
}

This should give you a good foundation for building Rust packages targeting WebAssembly with Nix. Remember to test your builds on both Linux and Darwin if you’re targeting multiple platforms.

1 Like