I am at a bit of an impasse for cross compilation with Nix.
Let’s use openssl as an example but this could go for any library and can be a
particular pain with Rust build scripts (build.rs) because they are actually
native programs that may want to dynamically link against a library instead of shelling out to a program on the $PATH.
However, the package also requires linking against openssl (aarch64) for a
build output program with the host architecture.
I can’t quite figure out a way to make this work with pkg-config and a funny
detail shows up here because the order of buildInputs matters for PKG_CONFIG_PATH and the first one it finds will be used, without any way to do
this architecture-dependent.
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
test =
{
stdenv,
openssl,
openssl-native ? openssl,
pkg-config,
}:
stdenv.mkDerivation {
nativeBuildInputs = [
pkg-config
openssl-native # if we put this here, pkg-config will do nothing with it
];
buildInputs = [
openssl-native # if we put this here, linking the aarch64 binary would fail
openssl
openssl-native # if we put this here, linking the x86 binary would fail
];
};
in
{
packages.test = pkgs.callPackage test { };
packages.test-aarch64 = pkgs.pkgsCross.aarch64-multiplatform.callPackage test {
openssl-native = pkgs.openssl; # we can do this to bypass the callPackage cross arch
};
};
The entire approach doesn’t feel great. Is there any way here that would actually work and maybe even be sane?
If you need to compile during the build for the buildPlatform, you should need openssl from pkgsBuildBuild in depsBuildBuild and then also a buildPlatform → buildPlatform rust compiler to build the build script for the buildPlatform. I don’t know how the rust mkDerivation wrapper works here though w.r.t. providing a second buildBuild rust compiler and using that to build the build script.
It would help if you posted a link to a concrete example that you’re trying to get working.
The sane approach is called crate2nix. Unfortunately buildRustPackage smashes all the crates – the packaged crate and all its dependencies – together into one big derivation.
With crate2nix you can express the fact that protofetch needs pkgsBuildBuild.openssl but t0ms-mystery-crate needs pkgsBuildHost.openssl.
Hmm that’s annoying. Would it perhaps be possible to split this drv into two: First compile the build script using pkgsBuildBuild.buildRustPackage and pkgsBuildBuild.openssl and then run it inside of the pkgsBuildHost.buildRustPackage of the actual drv?
buildRustPackage lets cargo control the build process. I don’t think there’s any way to tell cargo “hey, I already compiled build.rs; here’s the binary, please use this instead of compiling build.rs”. I’m sure you could trick cargo into using your prebuilt build.rs, but to do that you’d need to rely on things that cargo explicitly says aren’t stable and can change at any time, like it’s directory-naming scheme and the build artifact fingerprint algorithm.
I think this is why the Nix ecosystem has stabilized around basically only two approaches for building Rust packages:
Let cargo control everything (buildRustPackage)
Don’t use cargo for anything except querying https://crates.io (i.e. buildRustCrate / crate2nix)
These two extremes (“cargo everything” and “cargo nothing”) are the only ones that really work well. Unfortunately cargo isn’t like the UNIXy build processes where you have a bunch of small simple tools (make, grep, ld.so, etc) that each do one thing well and don’t know about each other.
With this I can now build both of these successfully: nix build .#packages.aarch64-linux.cross-thing-x86_64-linux nix build .#packages.x86_64-linux.cross-thing-aarch64-linux
I will have to check that out, I have used naersk and crane before but not this one.
Interesting, but this is also used by buildRustPackage since it’s part of nixpkgs, right? I will have to wrap my head around what’s happening here exactly.