summary refs log tree commit diff
path: root/pkgs/top-level/splice.nix
blob: a093442d3698c921128372b02ea5a01bfcbd9bd9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# The `splicedPackages' package set, and its use by `callPackage`
#
# The `buildPackages` pkg set is a new concept, and the vast majority package
# expression (the other *.nix files) are not designed with it in mind. This
# presents us with a problem with how to get the right version (build-time vs
# run-time) of a package to a consumer that isn't used to thinking so cleverly.
#
# The solution is to splice the package sets together as we do below, so every
# `callPackage`d expression in fact gets both versions. Each# derivation (and
# each derivation's outputs) consists of the run-time version, augmented with a
# `nativeDrv` field for the build-time version, and `crossDrv` field for the
# run-time version.
#
# We could have used any names we want for the disambiguated versions, but
# `crossDrv` and `nativeDrv` were somewhat similarly used for the old
# cross-compiling infrastructure. The names are mostly invisible as
# `mkDerivation` knows how to pull out the right ones for `buildDepends` and
# friends, but a few packages use them directly, so it seemed efficient (to
# @Ericson2314) to reuse those names, at least initially, to minimize breakage.
#
# For performance reasons, rather than uniformally splice in all cases, we only
# do so when `pkgs` and `buildPackages` are distinct. The `actuallySplice`
# parameter there the boolean value of that equality check.
lib: pkgs: actuallySplice:

let

  spliceReal = { pkgsBuildBuild, pkgsBuildHost, pkgsBuildTarget
               , pkgsHostHost, pkgsHostTarget
               , pkgsTargetTarget
               }: let
    mash =
      # Other pkgs sets
      pkgsBuildBuild // pkgsBuildTarget // pkgsHostHost // pkgsTargetTarget
      # The same pkgs sets one probably intends
      // pkgsBuildHost // pkgsHostTarget;
    merge = name: {
      inherit name;
      value = let
        defaultValue = mash.${name};
        # `or {}` is for the non-derivation attsert splicing case, where `{}` is the identity.
        valueBuildBuild = pkgsBuildBuild.${name} or {};
        valueBuildHost = pkgsBuildHost.${name} or {};
        valueBuildTarget = pkgsBuildTarget.${name} or {};
        valueHostHost = throw "`valueHostHost` unimplemented: pass manually rather than relying on splice.";
        valueHostTarget = pkgsHostTarget.${name} or {};
        valueTargetTarget = pkgsTargetTarget.${name} or {};
        augmentedValue = defaultValue
          # TODO(@Ericson2314): Stop using old names after transition period
          // (lib.optionalAttrs (pkgsBuildHost ? ${name}) { nativeDrv = valueBuildHost; })
          // (lib.optionalAttrs (pkgsHostTarget ? ${name}) { crossDrv = valueHostTarget; })
          // {
            __spliced =
                 (lib.optionalAttrs (pkgsBuildBuild ? ${name}) { buildBuild = valueBuildBuild; })
              // (lib.optionalAttrs (pkgsBuildTarget ? ${name}) { buildTarget = valueBuildTarget; })
              // { hostHost = valueHostHost; }
              // (lib.optionalAttrs (pkgsTargetTarget ? ${name}) { targetTarget = valueTargetTarget;
          });
        };
        # Get the set of outputs of a derivation. If one derivation fails to
        # evaluate we don't want to diverge the entire splice, so we fall back
        # on {}
        tryGetOutputs = value0: let
          inherit (builtins.tryEval value0) success value;
        in getOutputs (lib.optionalAttrs success value);
        getOutputs = value: lib.genAttrs
          (value.outputs or (lib.optional (value ? out) "out"))
          (output: value.${output});
      in
        # The derivation along with its outputs, which we recur
        # on to splice them together.
        if lib.isDerivation defaultValue then augmentedValue // spliceReal {
          pkgsBuildBuild = tryGetOutputs valueBuildBuild;
          pkgsBuildHost = tryGetOutputs valueBuildHost;
          pkgsBuildTarget = tryGetOutputs valueBuildTarget;
          pkgsHostHost = tryGetOutputs valueHostHost;
          pkgsHostTarget = getOutputs valueHostTarget;
          pkgsTargetTarget = tryGetOutputs valueTargetTarget;
        # Just recur on plain attrsets
        } else if lib.isAttrs defaultValue then spliceReal {
          pkgsBuildBuild = valueBuildBuild;
          pkgsBuildHost = valueBuildHost;
          pkgsBuildTarget = valueBuildTarget;
          pkgsHostHost = {};
          pkgsHostTarget = valueHostTarget;
          pkgsTargetTarget = valueTargetTarget;
        # Don't be fancy about non-derivations. But we could have used used
        # `__functor__` for functions instead.
        } else defaultValue;
    };
  in lib.listToAttrs (map merge (lib.attrNames mash));

  splicePackages = { pkgsBuildBuild, pkgsBuildHost, pkgsBuildTarget
                   , pkgsHostHost, pkgsHostTarget
                   , pkgsTargetTarget
                   } @ args:
    if actuallySplice then spliceReal args else pkgsHostTarget;

  splicedPackages = splicePackages {
    inherit (pkgs)
      pkgsBuildBuild pkgsBuildHost pkgsBuildTarget
      pkgsHostHost pkgsHostTarget
      pkgsTargetTarget
      ;
  } // {
    # These should never be spliced under any circumstances
    inherit (pkgs)
      pkgsBuildBuild pkgsBuildHost pkgsBuildTarget
      pkgsHostHost pkgsHostTarget
      pkgsTargetTarget
      buildPackages pkgs targetPackages
      ;
    inherit (pkgs.stdenv) buildPlatform targetPlatform hostPlatform;
  };

  splicedPackagesWithXorg = splicedPackages // builtins.removeAttrs splicedPackages.xorg [
    "callPackage" "newScope" "overrideScope" "packages"
  ];

in

{
  inherit splicePackages;

  # We use `callPackage' to be able to omit function arguments that can be
  # obtained `pkgs` or `buildPackages` and their `xorg` package sets. Use
  # `newScope' for sets of packages in `pkgs' (see e.g. `gnome' below).
  callPackage = pkgs.newScope {};

  callPackages = lib.callPackagesWith splicedPackagesWithXorg;

  newScope = extra: lib.callPackageWith (splicedPackagesWithXorg // extra);

  # Haskell package sets need this because they reimplement their own
  # `newScope`.
  __splicedPackages = splicedPackages // { recurseForDerivations = false; };
}