Jump to content

Cross Compiling

From NixOS Wiki
Revision as of 17:47, 12 December 2021 by imported>AleXoundOS (fix typo: Stack linking -> Static linking)
☶︎
This article or section needs to be expanded. Further information may be found in the related discussion page. Please consult the pedia article metapage for guidelines on contributing.

For building arm software check out the Article NixOS on ARM
If you are looking for building 32bit software, check out Packaging/32bit Applications
Quick example to cross compile a package: Cheatsheet#Cross-compile_packages.

Cross-Compiling a package in nixpkgs

Cross-compilation is well supported in nixpkgs since 18.09. The basic idea is to use pkgsCross.platform instead of pkgs:

nix-build '<nixpkgs>' -A pkgsCross.raspberryPi.openssl

How to obtain a shell with a cross compiler

Create a file crossShell.nix as follows:

with import <nixpkgs> {
  crossSystem = {
    config = "aarch64-unknown-linux-gnu";
  };
};

mkShell {
  buildInputs = [ zlib ]; # your dependencies here
}

and then use it to obtain a shell:

nix-shell crossShell.nix

The resulting shell contains a cross toolchain and zlib in this example. Note that contrary to native shells, the compiler and some other tools are prefixed: there is no gcc but a aarch64-unknown-linux-gnu-gcc. Some convenience environment variables expand to the prefixed version of tools: $CC, $LD...

Examples of how to specify your target system can be found in lib/systems/examples.nix. If the exact system you are targeting is available in this file then you can use the existing definition as in the following example:

let pkgs = import <nixpkgs> {
    crossSystem = (import <nixpkgs/lib>).systems.examples.armv7l-hf-multiplatform;
};
in
 mkShell {}

Even shorter:

let pkgs = import <nixpkgs> {}; in
pkgs.pkgsCross.armv7l-hf-multiplatform.mkShell {}

The examples above do not work as is with build dependencies (nativeBuildInputs). A solution is to use callPackage to enable splicing:

let pkgs = import <nixpkgs> {
  crossSystem = {
    config = "aarch64-unknown-linux-gnu";
  };
};
in
  pkgs.callPackage (
    {mkShell, pkg-config, zlib}:
    mkShell {
      nativeBuildInputs = [ pkg-config ]; # you build dependencies here
      buildInputs = [ zlib ]; # your dependencies here
    }
  ) {}

See also 🚩︎#49526.

Lazy cross-compiling

If you target "aarch64-unknown-linux-gnu", there is a nice way to reduce amount of cross-compiling and side-step journey to fix cross errors. The idea is to fetch non-essential dependencies from binary cache of regular aarch64 binaries.

Say we are building SDL2.

let
    # this will use aarch64 binaries from binary cache, so no need to build those
    pkgsArm = import <nixpkgs> {
        config = {};
        overlays = [];
        system = "aarch64-linux";
    };

    # these will be your cross packages
    pkgsCross = import <nixpkgs> {

       overlays = [(self: super: {

         # we want to hack on SDL, don't want to hack on those. Some even don't cross-compile
         inherit (pkgsArm)
           xorg libpulseaudio libGL guile systemd libxkbcommon
         ;
 
       })];
       crossSystem = {
         config = "aarch64-unknown-linux-gnu";
       };
     };

in pkgsCross.SDL2.override { 
      # those shouldn't be neither pkgsCross, nor pkgsArm
      # because those trigger
      #     cannot execute binary file: Exec format error
      # in this case it was enough to just use buildPackages variants
      # but in general there may be problems
      inherit (pkgsCross.buildPackages) 
         wayland wayland-protocols
      ;
   }


How to specify dependencies

Depending in which if packages are required at build time or at runtime they need to go to different inputs the derivation.

  • If it is used at build-time it's depsBuildXXX
    • compiler producing native binaries go to depsBuildBuild
    • compiler producing cross binaries, all setup hooks and programs executed by the builder go to depsBuildHost
      • common examples: pkg-config, autoreconfHook, makeWrapper, intltool, bison, flex
  • If it is used at run-time it's depsHostXXX. [Static linking doesn't effect this, even if it allows us to forget where things came from.]
    • if it’s an interpreter that will be needed by an installed script, it should go in depsHostTarget.
    • otherwise it is probably only needed at build time and can go in depsBuildHost
  • If it is a tool and "acts" (e.g. helps build) on build-time stuff, then it's depsXXXBuild
  • If it is a tool and "acts" on run-time stuff, then it's depsXXXHost
  • if it is not a tool, it's depsXXX(XXX+1)(build + 1 == host, host +1 == target) for backwards compatibility, use nativeBuildInputs instead of depsBuildHost and buildInputs instead of depsHostTarget.

Source: https://github.com/NixOS/nixpkgs/pull/50881#issuecomment-440772499


References

Additional resources