Unable to compile Rust with MSVC

With cargo-xwin I can successfully build my project to MSVC target. But I’m unable to write Nix derivation to do it using crane.

I tried to replicate cargo-xwin behaviour using source code

packages.ferma-win = let
  # acquired via `xwin download, unpack, splat`
  wlibs = pkgs.stdenvNoCC.mkDerivation {
    name = "windows-libs";
    nativeBuildInputs = [pkgs.zstd];
    src = ./windows-libs.tar.zst;
    sourceRoot = ".";
    installPhase = "mkdir $out && cp -r ./* $out";
  };

  craneLib = inputs.crane.lib.${system};
  crane = craneLib.overrideToolchain rust;
  project = crane.crateNameFromCargoToml {cargoToml = ./Cargo.toml;};
  src = crane.cleanCargoSource (crane.path ./.);

  target = "x86_64-pc-windows-msvc";
  env_target = "x86_64_pc_windows_msvc";

  common =
    project
    // rec {
      inherit src;
      strictDeps = true;
      doCheck = false;
      buildInputs = with pkgs; [pkg-config libllvm clang libclang lld openssl openssl.dev wlibs];
      nativeBuildInputs = buildInputs;
      depsBuildBuild = buildInputs;
      CARGO_BUILD_TARGET = target;
      cargoExtraArgs = "-vvv";
      RUSTFLAGS = lib.concatStringsSep " " [
        "-Clinker-flavor=lld-link"
        "-Lnative=${wlibs}/crt/lib/x86_64"
        "-Lnative=${wlibs}/sdk/lib/um/x86_64"
        "-Lnative=${wlibs}/sdk/lib/ucrt/x86_64"
      ];
      TARGET_CC = with pkgs; "${pkgs.stdenv.cc}/bin/${pkgs.stdenv.cc.targetPrefix}cc";
      # "TARGET_CC" = "clang-cl";
      "TARGET_CXX" = "clang-cl";
      "CC_${env_target}" = "clang-cl";
      "CCX_${env_target}" = "clang-cl";
      "TARGET_AR" = "llvm-lib";
      "AR_${env_target}" = "llvm-lib";
      "CARGO_TARGET_${lib.toUpper env_target}_LINKER" = "lld-link";
      "CARGO_TARGET_${lib.toUpper env_target}_RUNNER" = lib.toUpper env_target;
      "CL_FLAGS" = "--target=${target} -Wno-unused-command-line-argument -fuse-ld=lld-link /imsvc${wlibs}/crt/include /imsvc${wlibs}/sdk/include/ucrt /imsvc${wlibs}/sdk/include/um /imsvc${wlibs}/sdk/include/shared";
      "CFLAGS_${env_target}" = CL_FLAGS;
      "CXXFLAGS_${env_target}" = CL_FLAGS;
      "BINDGEN_EXTRA_CLANG_ARGS_${env_target}" = "-I${wlibs}/crt/include -I${wlibs}/sdk/include/ucrt -I${wlibs}/sdk/include/um -I${wlibs}/sdk/include/shared";
      "RC_FLAGS" = "-I${wlibs}/crt/include -I${wlibs}/sdk/include/ucrt -I${wlibs}/sdk/include/um -I${wlibs}/sdk/include/shared";
      # "CARGO_ENCODED_RUSTFLAGS" = builtins.replaceStrings [" "] ["\\x1f"] RUSTFLAGS;
    };
  artifacts = crane.buildDepsOnly common;
in
  crane.buildPackage (common
    // {
      src = fileset.toSource {
        root = ./.;
        fileset = fileset.unions [
          ./src/script.js
          ./src/dev.js
          (fileset.fromSource (cleanSourceWith {
            src = ./.;
            filter = craneLib.filterCargoSources;
          }))
        ];
      };
      cargoArtifacts = artifacts;
    });

output of sucessful build via cargo-xwin
https://0x0.st/Xrck.txt

output of failed build in Nix
https://0x0.st/XrcD.txt

Most notable:

[ring 0.17.8] cargo:warning=crypto/curve25519/../internal.h(132,10): fatal error: 'stdalign.h' file not found
[ring 0.17.8] cargo:warning=  132 | #include <stdalign.h>
[ring 0.17.8] cargo:warning=      |          ^~~~~~~~~~~~
[ring 0.17.8] cargo:warning=1 error generated.
[ring 0.17.8] exit status: 1
[ring 0.17.8] cargo:warning=ToolExecError: Command "clang-cl" "-nologo" "-MD" "-O2" "-Brepro" "-m64" "--target=x86_64-pc-windows-msvc" "-Wno-unused-command-line-argument" "-fuse-ld=lld-link" "/imsvc/nix/store/arssalx44qdxjpmh124m62zn56fqglfx-windows-libs/crt/include" "/imsvc/nix/store/arssalx44qdxjpmh124m62zn56fqglfx-windows-libs/sdk/include/ucrt" "/imsvc/nix/store/arssalx44qdxjpmh124m62zn56fqglfx-windows-libs/sdk/include/um" "/imsvc/nix/store/arssalx44qdxjpmh124m62zn56fqglfx-windows-libs/sdk/include/shared" "-I" "include" "-I" "/build/source/target/x86_64-pc-windows-msvc/release/build/ring-cfc8ac3d1499ba92/out" "/Gy" "/Zc:wchar_t" "/Zc:forScope" "/Zc:inline" "/Wall" "/wd4127" "/wd4464" "/wd4514" "/wd4710" "/wd4711" "/wd4820" "/wd5045" "-DNDEBUG" "-Fo/build/source/target/x86_64-pc-windows-msvc/release/build/ring-cfc8ac3d1499ba92/out/fad98b632b8ce3cc-curve25519.o" "-c" "--" "crypto/curve25519/curve25519.c" with args "clang-cl" did not execute successfully (status code exit status: 1).cargo:warning=In file included from crypto/fipsmodule/aes/aes_nohw.c:17:
[ring 0.17.8] cargo:warning=crypto/fipsmodule/aes/../../internal.h(132,10): fatal error: 'stdalign.h' file not found

what you’re doing is complicated and unusual (I mention this in case you don’t know), so that’s probably why nobody has replied. it’s a cool thing and it would be neat if you get it to work! I’m happy to share my thoughts in case they help, but I can’t promise anything. I’ve never personally built against the MSVC targets…

it’s giving you a missing #include error. this sounds like there’s a C or C++ library which one of the crates you rely on is trying to find, but it’s failing to. you should figure out what library is supposed to provide stdalign.h, and whether it’s expected to run on Windows. if it is, you probably need to add it to the buildInputs. if it’s not, you may have to write some code yourself to provide a shim for the necessary functionality. unfortunately if it’s about memory alignment, that’s a very platform-specific topic, so it might require a lot of low-level ABI knowledge to go that route.

I hope that at least points you in the right direction. good luck!

1 Like

I was finally able to build. The only seem problem remains is crane unable to build acutal package:

wlibs17 = pkgs.stdenvNoCC.mkDerivation {
  name = "windows-libs-17";
  src = ./windows-libs-17.tar.xz;
  sourceRoot = ".";
  installPhase = "mkdir $out && cp -r ./* $out";
  phases = ["unpackPhase" "installPhase"];
};

let
  craneLib = inputs.crane.lib.${system};
  crane = craneLib.overrideToolchain rust;
  project = crane.crateNameFromCargoToml {cargoToml = ./Cargo.toml;};
  src = crane.cleanCargoSource (crane.path ./.);

  target = "x86_64-pc-windows-msvc";
  env_target = builtins.replaceStrings ["-"] ["_"] target;
  wlibs = wlibs17;

  sharedLibs = [
    "${wlibs}"
    "${wlibs}/sdk/lib"
    "${wlibs}/sdk/include"
    "${wlibs}/sdk/include/um"
    "${wlibs}/sdk/include/ucrt"
    "${wlibs}/sdk/include/shared"

    "${wlibs}/crt/lib"
    "${wlibs}/crt/include"

    "${wlibs}/crt/lib/x86_64"
    "${wlibs}/sdk/lib/um/x86_64"
    "${wlibs}/sdk/lib/ucrt/x86_64"

    "${pkgs.libclang.lib}/lib"
    "${pkgs.libclang.lib}/lib/clang/17/include"
    "${pkgs.stdenv.cc.libc}"
    "${pkgs.stdenv.cc.libc}/lib"
    "${pkgs.stdenv.cc.libc_lib}"
    "${pkgs.stdenv.cc.libc_lib}/lib"
  ];

  cmakefile = pkgs.writeText "override.cmake" ''
    set(CMAKE_SYSTEM_NAME Windows)
    set(CMAKE_SYSTEM_PROCESSOR x86_64)

    set(CMAKE_C_COMPILER clang-cl CACHE FILEPATH "")
    set(CMAKE_CXX_COMPILER clang-cl CACHE FILEPATH "")
    set(CMAKE_AR llvm-lib)
    set(CMAKE_LINKER lld-link CACHE FILEPATH "")

    set(COMPILE_FLAGS
        --target=${target}
        -Wno-unused-command-line-argument
        -fuse-ld=lld-link
        ${concatMapStringsSep "\n" (l: "/imsvc${l}") sharedLibs}
    )

    set(LINK_FLAGS
        /manifest:no
        ${concatMapStringsSep "\n" (l: ''-libpath:"${l}"'') sharedLibs}
    )

    string(REPLACE ";" " " COMPILE_FLAGS "''${COMPILE_FLAGS}")

    set(_CMAKE_C_FLAGS_INITIAL "''${CMAKE_C_FLAGS}" CACHE STRING "")
    set(CMAKE_C_FLAGS "''${_CMAKE_C_FLAGS_INITIAL} ''${COMPILE_FLAGS}" CACHE STRING "" FORCE)

    set(_CMAKE_CXX_FLAGS_INITIAL "''${CMAKE_CXX_FLAGS}" CACHE STRING "")
    set(CMAKE_CXX_FLAGS "''${_CMAKE_CXX_FLAGS_INITIAL} ''${COMPILE_FLAGS}" CACHE STRING "" FORCE)

    string(REPLACE ";" " " LINK_FLAGS "''${LINK_FLAGS}")

    set(_CMAKE_EXE_LINKER_FLAGS_INITIAL "''${CMAKE_EXE_LINKER_FLAGS}" CACHE STRING "")
    set(CMAKE_EXE_LINKER_FLAGS "''${_CMAKE_EXE_LINKER_FLAGS_INITIAL} ''${LINK_FLAGS}" CACHE STRING "" FORCE)

    set(_CMAKE_MODULE_LINKER_FLAGS_INITIAL "''${CMAKE_MODULE_LINKER_FLAGS}" CACHE STRING "")
    set(CMAKE_MODULE_LINKER_FLAGS "''${_CMAKE_MODULE_LINKER_FLAGS_INITIAL} ''${LINK_FLAGS}" CACHE STRING "" FORCE)

    set(_CMAKE_SHARED_LINKER_FLAGS_INITIAL "''${CMAKE_SHARED_LINKER_FLAGS}" CACHE STRING "")
    set(CMAKE_SHARED_LINKER_FLAGS "''${_CMAKE_SHARED_LINKER_FLAGS_INITIAL} ''${LINK_FLAGS}" CACHE STRING "" FORCE)

    set(CMAKE_C_STANDARD_LIBRARIES "" CACHE STRING "" FORCE)
    set(CMAKE_CXX_STANDARD_LIBRARIES "" CACHE STRING "" FORCE)

    set(CMAKE_TRY_COMPILE_CONFIGURATION Release)
  '';

  common =
    project
    // rec {
      inherit src;

      strictDeps = true;
      doCheck = false;

      buildInputs = with pkgs; [clang libclang libclang.lib libllvm lld openssl openssl.dev wlibs];
      nativeBuildInputs = with pkgs; [pkg-config] ++ buildInputs;
      depsBuildBuild = nativeBuildInputs;

      "RUSTFLAGS" = concatStringsSep " " ([
          "-Clinker-flavor=lld-link"
        ]
        ++ map (l: "-Lnative=${l}") sharedLibs);

      "CARGO_BUILD_TARGET" = target;
      "CARGO_BUILD_RUSTFLAGS" = RUSTFLAGS;
      "CARGO_TARGET_${lib.toUpper env_target}_LINKER" = "lld-link";
      "CARGO_TARGET_${lib.toUpper env_target}_RUNNER" = lib.toUpper env_target;

      "TARGET_CC" = "clang-cl";
      "TARGET_CXX" = "clang-cl";
      "CC_${env_target}" = "clang-cl";
      "CCX_${env_target}" = "clang-cl";
      "TARGET_AR" = "llvm-lib";
      "AR_${env_target}" = "llvm-lib";

      "CL_FLAGS" = lib.concatStringsSep " " ([
          "--target=${target}"
          "-Wno-unused-command-line-argument"
          "-fuse-ld=lld-link"
        ]
        ++ map (str: "/imsvc${str}") sharedLibs);
      "CFLAGS_${env_target}" = CL_FLAGS;
      "CXXFLAGS_${env_target}" = CL_FLAGS;

      "RC_FLAGS" = concatStringsSep " " ([] ++ map (str: "-I${str}") sharedLibs);
      "BINDGEN_EXTRA_CLANG_ARGS_${env_target}" = RC_FLAGS;

      "CMAKE_TOOLCHAIN_FILE_${env_target}" = cmakefile;
      "CMAKE_SYSTEM_NAME" = "Windows";
      "CMAKE_GENERATOR" = "Ninja";
    };
in
  crane.buildPackage (common
    // {
      src = fileset.toSource {
        root = ./.;
        fileset = fileset.unions [
          ./src/script.js
          ./src/dev.js
          ./resources.res
          ./resources.rc
          ./ic.ico
          (fileset.fromSource (cleanSourceWith {
            src = ./.;
            filter = craneLib.filterCargoSources;
          }))
        ];
      };
      dontFixup = true;
      dontStrip = true;
      # dontPatch = true;
    });

It produces actual .exe binary after building deps, but then crane tries to build actual package from deps and there is an error (simplified):

error: linking with `lld-link` failed: exit status: 1
...
lld-link: error: undefined symbol: _mm_loadu_si128
lld-link: error: undefined symbol: _mm_load_si128
lld-link: error: undefined symbol: _mm_cvtsi128_si32

and so on

But again, If in archive produced by previous step there is actual .exe file (I didn’t test it yet but it exists)

Probably there is a lot of unneded crap, but I don’t know and afraid to remove it.

Finally I was able to build it:

let
  craneLib = inputs.crane.lib.${system};
  crane = craneLib.overrideToolchain rust;
  project = crane.crateNameFromCargoToml {cargoToml = ./Cargo.toml;};

  target = "x86_64-pc-windows-msvc";
  envTarget = builtins.replaceStrings ["-"] ["_"] target;

  wlibs = pkgs.stdenvNoCC.mkDerivation {
    name = "windows-libs-17";
    src = ./windows-libs-17.tar.xz;
    sourceRoot = ".";
    installPhase = "mkdir $out && cp -R ./* $out";
    phases = ["unpackPhase" "installPhase"];
  };

  libs = [
    "${pkgs.libclang.lib}/lib/clang/17/include" # PLACE IT FIRST!!!

    "${wlibs}"
    "${wlibs}/sdk/lib"
    "${wlibs}/sdk/include"
    "${wlibs}/sdk/include/um"
    "${wlibs}/sdk/include/ucrt"
    "${wlibs}/sdk/include/shared"

    "${wlibs}/crt/lib"
    "${wlibs}/crt/include"

    "${wlibs}/crt/lib/x86_64"
    "${wlibs}/sdk/lib/um/x86_64"
    "${wlibs}/sdk/lib/ucrt/x86_64"
  ];

  common =
    project
    // rec {
      src = fileset.toSource {
        root = ./.;
        fileset = fileset.unions [
          ./src/script.js
          ./src/dev.js
          ./resources.res
          ./resources.rc
          ./ic.ico
          (fileset.fromSource (cleanSourceWith {
            src = ./.;
            filter = craneLib.filterCargoSources;
          }))
        ];
      };

      doCheck = false;

      buildInputs = with pkgs; [clang libclang libclang.lib libllvm lld openssl openssl.dev wlibs];
      nativeBuildInputs = with pkgs; [pkg-config] ++ buildInputs;
      depsBuildBuild = nativeBuildInputs;

      "CARGO_BUILD_RUSTFLAGS" = concatStringsSep " " ([
          "-Clinker-flavor=lld-link"
        ]
        ++ map (l: "-Lnative=${l}") libs);
      "CARGO_BUILD_TARGET" = target;
      "CARGO_TARGET_${toUpper envTarget}_LINKER" = "lld-link";
      "CARGO_TARGET_${toUpper envTarget}_RUNNER" = toUpper envTarget;

      "TARGET_CC" = "clang-cl";
      "TARGET_CXX" = TARGET_CC;
      "CC_${envTarget}" = TARGET_CC;
      "CCX_${envTarget}" = TARGET_CC;

      "TARGET_AR" = "llvm-lib";
      "AR_${envTarget}" = TARGET_AR;

      "CL_FLAGS" = concatStringsSep " " ([
          "--target=${target}"
          "-Wno-unused-command-line-argument"
          "-fuse-ld=lld-link"
        ]
        ++ map (str: "/imsvc${str}") libs);
      "CFLAGS_${envTarget}" = CL_FLAGS;
      "CXXFLAGS_${envTarget}" = CL_FLAGS;
      "RC_FLAGS" = concatStringsSep " " ([] ++ map (str: "-I${str}") libs);
      "BINDGEN_EXTRA_CLANG_ARGS_${envTarget}" = RC_FLAGS;

      dontFixup = true;
      dontStrip = true;
    };

  cargoArtifacts = crane.buildDepsOnly common;
in
  crane.buildPackage (common // {inherit cargoArtifacts;});