Skip to content

simpler, saner cross-compilation #227327

@ghost

Description

This is a long-term goal; none of this can happen overnight. This is also an incomplete work-in-progress.

TL;DR: Short Example

{ pkgsOnHost          # simple dependencies
, pkgsOnBuild         # build tools that don't emit code (`grep`, `jq`...)
, pkgsOnBuildForHost  # build tools that emit code for the hostPlatform
}:

{

  # setup.sh will put this stuff in the $PATH at compile time
  depsInPATH = with pkgsOnBuild; [
    cmake
    makeWrapper
    pkgsOnBuildForHost.gfortran     # to compile fortran code for use at run time
  ];

  # setup.sh will not put any of this stuff into the $PATH
  deps = with pkgsOnHost; [
    openssl
  ];

}

Wishlist

Eliminate splicing

I made splicing (nativeDrv and crossDrv are older, but were done differently). Splicing is bad. I never wanted it. I causes major headaches when getting anywhere near bootstrapping. It is gross. It is probably slow. It should be removed. -- #204303 (comment)

Splicing is too magical. Probably three1 people really understand it. Dozens of people think they understand it but really don't, and get pissed off when it breaks in weird ways. This is a major part of why people hate on cross compilation.

Eliminating splicing likely means that package expressions will need to take package sets explicitly as top-level arguments. See next section.

Eliminate special exceptions to depsFooBar naming

Basically, do this (thanks @Artturin):

targetPlatform==null for packages that do not emit code

The vast majority of packages in nixpkgs don't emit code. Instead of setting their targetPlatform equal to their hostPlatform, we should set it to null.

Using targetPlatform==hostPlatform erases the distinction between packages that emit code for their hostPlatform and packages that don't emit code. It also creates ambiguity: these packages can be moved freely between (say) depsBuildHost and depsBuildTarget with no change in behavior, so the difference between depsBuildHost and depsBuildTarget is harder for people to learn.

Prefix all binaries that have a targetPlatform

AKA resurrect this PR:

Right now we have an artificial distinction between:

  1. pkgsBuildTarget for build=X target=Y
  2. pkgsHostTarget for host=X target=Y

The first kind won't have a aarch64-linux- prefix on their code-emitting binaries (like gcc). The latter kind will. This is silly. Prefix all the binaries, eliminate the distinction.

For compilers (clang, rust) that have a single binary entry point for all platforms and use some kind of --target= flag, we simply wrap that entry point with a wrapper for each targetPlatform which adds the --target= flag.

Use the pythonPackages onBuildForHost naming

pkgsFooBar is extremely unergonomic. Nobody can remember what Foo is for and what Bar is for. These should be changed to pkgsOnBuildForHost like pythonPackages does; with pkgsOnFooForBar least that way there is a reminder of what Foo and Bar are: pkgsOnFooForBar runs on Foo and emits code for Bar.

Packages with no target (see previous heading) get shorter names: pkgsOnBuild and pkgsOnHost.

No more depsOnXForY, only pkgsOnXForY

The distinction between the various depsFooBar attributes of a derivation serves two purposes:

  1. "Desplicing" the correct derivation from the spliced package
  2. Ensuring that only depsBuildX stuff goes in the $PATH at build time

Instead of (six? more?) different derivation attributes we only need two:

depsInPATH  = [ ... ]  # stuff that is put into $PATH
deps        = [ ... ]  # stuff that is not put into $PATH

Long Example

This example might look complicated, but it exercises all possible arguments and attributes. Think about that. This is the most complicated attrset you'll ever see.

Non-code-emitting packages will only have, at most, the first five attributes (one of which is never used in practice). In practice most packages will have only three arguments: pkgsOnHost, pkgsOnBuild, and pkgsOnBuildForHost.

{ pkgsOnHost          # simple dependencies
, pkgsOnBuild         # build tools that don't emit code (`grep`, `jq`...)
, pkgsOnBuildForHost  # build tools that emit code for the hostPlatform
# the three arguments above are enough for nearly all packages in nixpkgs

, pkgsOnHostForHost   # theoretical possibility only: zero uses in nixpkgs
, pkgsOnBuildForBuild # build tools that emit code for the buildPlatform

# **only code-emitting packages** (compilers) will have the following:
, pkgsOnHostForTarget   # theoretical posibility only: canadian cross
, pkgsOnBuildForTarget  # e.g. the bintools used to link the libraries bundled with gcc
, pkgsOnTarget          # target libraries bundled with the compiler (eg libstdc++)
, pkgsOnTargetForTarget # code-emitting target libraries (JITs), like libgccjit
}:

{

  # setup.sh will put this stuff in the $PATH at compile time
  depsInPATH = with pkgsOnBuild; [
    cmake
    makeWrapper
    pkgsOnBuildForHost.gfortran     # to compile fortran code for use at run time
    pkgsOnBuildForBuild.stdenv.cc   # to compile+run a C program at build time
  ];

  # setup.sh will not put any of this stuff into the $PATH
  deps = with pkgsOnHost; [
    openssl
    pkgsOnTarget.libstdc++
  ];

}

Problems

  • Note: there is a possible solution to both of the following two problems, which brings additional benefits.

    1. This needs to be harmonized with callPackage. It is really unfortunate that the Nix language does not let functions declare deep attributes as arguments, like {foo.bar, ...}: ....

    2. In its current form this makes .override very painful to use, since it don't cope well with overriding a sub-attribute of an argument. It should be extended to provide an ergonomic way to override sub-attributes of an argument.

Questions

  1. Should deps and depsInPATH be combined into a single attribute? mkDerivation could decide whether or not to put each dependency into the $PATH based on whether stdenv.buildPlatform.canExecute that dependency's hostPlatform.

    In practice there are very very very few situations where you don't want all the possibly-executable dependencies put into the path; we could provide an "escape hatch" to mark specific dependencies as "don't belong in the $PATH even if we could execute them".

  2. with is a footgun, and this scheme encourages with pkgsOnBuild. We could make deps and depsOnBuild be attrsets (whose attrnames are ignored); this would allow the use of the much-safer inherit syntax. A better long-term solution would be RFC 110.

  3. Should pkgsOnBuildForHost be an attribute within the pkgsOnBuild attrset? For example, pkgsOnBuild.forHost. This would mean only two arguments for non-compiler-like packages.

Footnotes

  1. I am not one of these people. But I'm also aware that I don't fully understand splicing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    0.kind: enhancementAdd something new or improve an existing system.1.severity: significantNovel ideas, large API changes, notable refactorings, issues with RFC potential, etc.2.status: stalehttps://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md6.topic: cross-compilationBuilding packages on a different platform than they will be used on6.topic: stdenvStandard environment

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions