Merge pull request #312731 from fabaff/hickle-refactor
[NixPkgs.git] / pkgs / stdenv / booter.nix
blob7fc1fa42b965c238974f44c6ae9630b375b731f6
1 # This file defines a single function for booting a package set from a list of
2 # stages. The exact mechanics of that function are defined below; here I
3 # (@Ericson2314) wish to describe the purpose of the abstraction.
5 # The first goal is consistency across stdenvs. Regardless of what this function
6 # does, by making every stdenv use it for bootstrapping we ensure that they all
7 # work in a similar way. [Before this abstraction, each stdenv was its own
8 # special snowflake due to different authors writing in different times.]
10 # The second goal is consistency across each stdenv's stage functions. By
11 # writing each stage in terms of the previous stage, commonalities between them
12 # are more easily observable. [Before, there usually was a big attribute set
13 # with each stage, and stages would access the previous stage by name.]
15 # The third goal is composition. Because each stage is written in terms of the
16 # previous, the list can be reordered or, more practically, extended with new
17 # stages. The latter is used for cross compiling and custom
18 # stdenvs. Additionally, certain options should by default apply only to the
19 # last stage, whatever it may be. By delaying the creation of stage package sets
20 # until the final fold, we prevent these options from inhibiting composition.
22 # The fourth and final goal is debugging. Normal packages should only source
23 # their dependencies from the current stage. But for the sake of debugging, it
24 # is nice that all packages still remain accessible. We make sure previous
25 # stages are kept around with a `stdenv.__bootPackges` attribute referring the
26 # previous stage. It is idiomatic that attributes prefixed with `__` come with
27 # special restrictions and should not be used under normal circumstances.
28 { lib, allPackages }:
30 # Type:
31 #   [ pkgset -> (args to stage/default.nix) or ({ __raw = true; } // pkgs) ]
32 #   -> pkgset
34 # In english: This takes a list of function from the previous stage pkgset and
35 # returns the final pkgset. Each of those functions returns, if `__raw` is
36 # undefined or false, args for this stage's pkgset (the most complex and
37 # important arg is the stdenv), or, if `__raw = true`, simply this stage's
38 # pkgset itself.
40 # The list takes stages in order, so the final stage is last in the list. In
41 # other words, this does a foldr not foldl.
42 stageFuns: let
44   /* "dfold" a ternary function `op' between successive elements of `list' as if
45      it was a doubly-linked list with `lnul' and `rnul` base cases at either
46      end. In precise terms, `dfold op lnul rnul [x_0 x_1 x_2 ... x_n-1]` is the
47      same as
49        let
50          f_-1  = lnul f_0;
51          f_0   = op f_-1   x_0  f_1;
52          f_1   = op f_0    x_1  f_2;
53          f_2   = op f_1    x_2  f_3;
54          ...
55          f_n   = op f_n-1  x_n  f_n+1;
56          f_n+1 = rnul f_n;
57        in
58          f_0
59   */
60   dfold = op: lnul: rnul: list:
61     let
62       len = builtins.length list;
63       go = pred: n:
64         if n == len
65         then rnul pred
66         else let
67           # Note the cycle -- call-by-need ensures finite fold.
68           cur  = op pred (builtins.elemAt list n) succ;
69           succ = go cur (n + 1);
70         in cur;
71       lapp = lnul cur;
72       cur = go lapp 0;
73     in cur;
75   # Take the list and disallow custom overrides in all but the final stage,
76   # and allow it in the final flag. Only defaults this boolean field if it
77   # isn't already set.
78   withAllowCustomOverrides = lib.lists.imap1
79     (index: stageFun: prevStage:
80       # So true by default for only the first element because one
81       # 1-indexing. Since we reverse the list, this means this is true
82       # for the final stage.
83       { allowCustomOverrides = index == 1; }
84       // (stageFun prevStage))
85     (lib.lists.reverseList stageFuns);
87   # Adds the stdenv to the arguments, and sticks in it the previous stage for
88   # debugging purposes.
89   folder = nextStage: stageFun: prevStage: let
90     args = stageFun prevStage;
91     args' = args // {
92       stdenv = args.stdenv // {
93         # For debugging
94         __bootPackages = prevStage;
95         __hatPackages = nextStage;
96       };
97     };
98     thisStage =
99       if args.__raw or false
100       then args'
101       else allPackages ((builtins.removeAttrs args' ["selfBuild"]) // {
102         adjacentPackages = if args.selfBuild or true then null else rec {
103           pkgsBuildBuild = prevStage.buildPackages;
104           pkgsBuildHost = prevStage;
105           pkgsBuildTarget =
106             if args.stdenv.targetPlatform == args.stdenv.hostPlatform
107             then pkgsBuildHost
108             else assert args.stdenv.hostPlatform == args.stdenv.buildPlatform; thisStage;
109           pkgsHostHost =
110             if args.stdenv.hostPlatform == args.stdenv.targetPlatform
111             then thisStage
112             else assert args.stdenv.buildPlatform == args.stdenv.hostPlatform; pkgsBuildHost;
113           pkgsTargetTarget = nextStage;
114         };
115       });
116   in thisStage;
118   # This is a hack for resolving cross-compiled compilers' run-time
119   # deps. (That is, compilers that are themselves cross-compiled, as
120   # opposed to used to cross-compile packages.)
121   postStage = buildPackages: {
122     __raw = true;
123     stdenv.cc =
124       if buildPackages.stdenv.hasCC
125       then
126         if buildPackages.stdenv.cc.isClang or false
127         # buildPackages.clang checks targetPackages.stdenv.cc (i. e. this
128         # attribute) to get a sense of the its set's default compiler and
129         # chooses between libc++ and libstdc++ based on that. If we hit this
130         # code here, we'll cause an infinite recursion. Since a set with
131         # clang as its default compiler always means libc++, we can infer this
132         # decision statically.
133         then buildPackages.llvmPackages.libcxxClang
134         else buildPackages.gcc
135       else
136         # This will blow up if anything uses it, but that's OK. The `if
137         # buildPackages.stdenv.cc.isClang then ... else ...` would blow up
138         # everything, so we make sure to avoid that.
139         buildPackages.stdenv.cc;
140   };
142 in dfold folder postStage (_: {}) withAllowCustomOverrides