I was trying to build a Windows test program for detailing a bug in Wine. Nix has fabulous cross compilation support, so I figured this should be easy to do. It is, but trying to figure out how to do it, was not.
The key non-obvious part is that you need to use callPackage
as it does some hackery to make the various versions
of each package available all under the same package name.
When you are cross compiling
buildInputs = [ foo ];
is foo
built for the other system while
nativeBuildInputs = [ foo ];
is foo
built for the local system. These are not the same foo
.
How can that be? mkDerivation
actually uses foo.__spliced.<version>
, if it is available, where <version>
is
picked depends on whether it was placed in buildInputs
or nativeBuildInputs
. As it is callPackage
that
augments foo
with these versions (see the code in
pkgs/top-level/splice.nix), it is
absolutely necessary it use callPackages
when build cross packages.
With that in mind, hopefully it is understandable that this will work
example = pkgs.pkgsCross.mingw32.callPackage (
{ stdenv, cmake }:
stdenv.mkDerivation {
name = "example";
version = "1.0.0";
src = ./.;
nativeBuildInputs = [ cmake ];
}
) { };
as callPackage
is augmenting cmake
to include the version built for the local system so nativeBuildInputs
can
use it, while this
example = with pkgs.pkgsCross.mingw32; stdenv.mkDerivation {
name = "example";
version = "1.0.0";
src = ./.;
nativeBuildInputs = [ cmake ];
}
) { };
will not as cmake
has not been augemented, so you are going to get the one built for the other system, which will
not be able to run on the local system as will be required to do the build.
Hopefully it is also obvious that
example = with pkgs.pkgsCross.mingw32.buildPackages; stdenv.mkDerivation {
name = "example";
version = "1.0.0";
src = ./.;
nativeBuildInputs = [ cmake ];
}
) { };
is not correct either. Although it gives you a cmake
built for the local system, it also switches to the stdenv
that was used to build mingw32
. This gives you the the compiler that was used to build mingw32
and not the
mingw32
compiler.
If you want to do it without the callPackage
magic, then you have to manually specifying where to get each
package from. This works
example = with pkgs.pkgsCross.mingw32; stdenv.mkDerivation {
name = "example";
version = "1.0.0";
src = ./.;
nativeBuildInputs = [ buildPackages.cmake ];
}
) { };
as that is essentially what the splicing magic is doing behind the scenes.
This repo includes a hello world program, a trivial
CMakeLists.txt file, and a nix flake with the
above code to show just how easy it is to cross compile it for Windows
once you know the callPackage
detail.
Give it a go. Easy to build
nix flake update
nix build .#hello
ls result/bin
and easy to run
nix shell nixpkgs#wine
wine result/bin/hello.exe
exit