summaryrefslogtreecommitdiffstats
path: root/pkgs/build-support/build-fhsenv-bubblewrap/buildFHSEnv.nix
blob: 21316d47d5d283a58d9fcb2e6aa516bedfd2b0d7 (plain)
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
{
  lib,
  stdenv,
  runCommandLocal,
  buildEnv,
  writeText,
  writeShellScriptBin,
  pkgs,
  pkgsHostTarget,
}:

{
  profile ? "",
  targetPkgs ? pkgs: [ ],
  multiPkgs ? pkgs: [ ],
  multiArch ? false, # Whether to include 32bit packages
  includeClosures ? false, # Whether to include closures of all packages
  nativeBuildInputs ? [ ],
  extraBuildCommands ? "",
  extraBuildCommandsMulti ? "",
  extraOutputsToInstall ? [ ],
  ... # for name, or pname+version
}@args:

# HOWTO:
# All packages (most likely programs) returned from targetPkgs will only be
# installed once--matching the host's architecture (64bit on x86_64 and 32bit on
# x86).
#
# Packages (most likely libraries) returned from multiPkgs are installed
# once on x86 systems and twice on x86_64 systems.
# On x86 they are merged with packages from targetPkgs.
# On x86_64 they are added to targetPkgs and in addition their 32bit
# versions are also installed. The final directory structure looks as
# follows:
# /lib32 will include 32bit libraries from multiPkgs
# /lib64 will include 64bit libraries from multiPkgs and targetPkgs
# /lib will link to /lib64

let
  # The splicing code does not handle `pkgsi686Linux` well, so we have to be
  # explicit about which package set it's coming from.
  inherit (pkgsHostTarget) pkgsi686Linux;

  name = if (args ? pname && args ? version) then "${args.pname}-${args.version}" else args.name;

  # "use of glibc_multi is only supported on x86_64-linux"
  isMultiBuild = multiArch && stdenv.system == "x86_64-linux";
  # What should $out/usr/lib be linked to: lib32 or lib64?
  defaultLib =
    if stdenv.hostPlatform.is64bit then
      "lib64"
    else if stdenv.hostPlatform.is32bit then
      "lib32"
    else
      throw "buildFHSEnvBubblewrap: defaultLib cannot handle system ${stdenv.hostPlatform.system} (expected 64bit or 32bit)";

  # list of packages (usually programs) which match the host's architecture
  # (which includes stuff from multiPkgs)
  targetPaths = targetPkgs pkgs ++ (if multiPkgs == null then [ ] else multiPkgs pkgs);

  # list of packages which are for x86 (only multiPkgs, only for x86_64 hosts)
  multiPaths = multiPkgs pkgsi686Linux;

  # base packages of the fhsenv
  # these match the host's architecture, glibc_multi is used for multilib
  # builds. glibcLocales must be before glibc or glibc_multi as otherwise
  # the wrong LOCALE_ARCHIVE will be used where only C.UTF-8 is available.
  baseTargetPaths = with pkgs; [
    glibcLocales
    (if isMultiBuild then glibc_multi else glibc)
    gcc.cc.lib
    bashInteractiveFHS
    coreutils
    less
    shadow
    su
    gawk
    diffutils
    findutils
    gnused
    gnugrep
    gnutar
    gzip
    bzip2
    xz
  ];
  baseMultiPaths = with pkgsi686Linux; [
    gcc.cc.lib
  ];

  ldconfig = writeShellScriptBin "ldconfig" ''
    # due to a glibc bug, 64-bit ldconfig complains about patchelf'd 32-bit libraries, so we use 32-bit ldconfig when we have them
    exec ${
      if isMultiBuild then pkgsi686Linux.glibc.bin else pkgs.glibc.bin
    }/bin/ldconfig -f /etc/ld.so.conf -C /etc/ld.so.cache "$@"
  '';

  etcProfile = pkgs.writeTextFile {
    name = "${name}-fhsenv-profile";
    destination = "/etc/profile";
    text = ''
      export PS1='${name}-fhsenv:\u@\h:\w\$ '
      export LOCALE_ARCHIVE="''${LOCALE_ARCHIVE:-/usr/lib/locale/locale-archive}"
      export PATH="/run/wrappers/bin:/usr/bin:/usr/sbin:$PATH"
      export TZDIR='/etc/zoneinfo'

      # XDG_DATA_DIRS is used by pressure-vessel (steam proton) and vulkan loaders to find the corresponding icd
      export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}/run/opengl-driver/share:/run/opengl-driver-32/share

      # Following XDG spec [1], XDG_DATA_DIRS should default to "/usr/local/share:/usr/share".
      # In nix, it is commonly set without containing these values, so we add them as fallback.
      #
      # [1] <https://specifications.freedesktop.org/basedir-spec/latest>
      case ":$XDG_DATA_DIRS:" in
        *:/usr/local/share:*) ;;
        *) export XDG_DATA_DIRS="$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}/usr/local/share" ;;
      esac
      case ":$XDG_DATA_DIRS:" in
        *:/usr/share:*) ;;
        *) export XDG_DATA_DIRS="$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}/usr/share" ;;
      esac

      # Force compilers and other tools to look in default search paths
      unset NIX_ENFORCE_PURITY
      export NIX_BINTOOLS_WRAPPER_TARGET_HOST_${stdenv.cc.suffixSalt}=1
      export NIX_CC_WRAPPER_TARGET_HOST_${stdenv.cc.suffixSalt}=1
      export NIX_CFLAGS_COMPILE="-idirafter /usr/include"''${NIX_CFLAGS_COMPILE:+" $NIX_CFLAGS_COMPILE"}
      export NIX_CFLAGS_LINK="-L/usr/lib -L/usr/lib32"''${NIX_CFLAGS_LINK:+" $NIX_CFLAGS_LINK"}
      export NIX_LDFLAGS="-L/usr/lib -L/usr/lib32"''${NIX_LDFLAGS:+" $NIX_LDFLAGS"}
      export PKG_CONFIG_PATH=/usr/lib/pkgconfig''${PKG_CONFIG_PATH:+":$PKG_CONFIG_PATH"}
      export ACLOCAL_PATH=/usr/share/aclocal''${ACLOCAL_PATH:+":$ACLOCAL_PATH"}

      # GStreamer searches for plugins relative to its real binary's location
      # https://gitlab.freedesktop.org/gstreamer/gstreamer/-/commit/bd97973ce0f2c5495bcda5cccd4f7ef7dcb7febc
      export GST_PLUGIN_SYSTEM_PATH_1_0=/usr/lib/gstreamer-1.0:/usr/lib32/gstreamer-1.0''${GST_PLUGIN_SYSTEM_PATH_1_0:+":$GST_PLUGIN_SYSTEM_PATH_1_0"}


      ${profile}
    '';
  };

  ensureGsettingsSchemasIsDirectory =
    runCommandLocal "fhsenv-ensure-gsettings-schemas-directory" { }
      ''
        mkdir -p $out/share/glib-2.0/schemas
        touch $out/share/glib-2.0/schemas/.keep
      '';

  # Shamelessly stolen (and cleaned up) from original buildEnv.
  # Should be semantically equivalent, except we also take
  # a list of default extra outputs that will be installed
  # for derivations that don't explicitly specify one.
  # Note that this is not the same as `extraOutputsToInstall`,
  # as that will apply even to derivations with an output
  # explicitly specified, so this does change the behavior
  # very slightly for that particular edge case.
  pickOutputs =
    let
      pickOutputsOne =
        outputs: drv:
        let
          isSpecifiedOutput = drv.outputSpecified or false;
          outputsToInstall = drv.meta.outputsToInstall or null;
          pickedOutputs =
            if isSpecifiedOutput || outputsToInstall == null then
              [ drv ]
            else
              map (out: drv.${out} or null) (outputsToInstall ++ outputs);
          extraOutputs = map (out: drv.${out} or null) extraOutputsToInstall;
          cleanOutputs = lib.filter (o: o != null) (pickedOutputs ++ extraOutputs);
        in
        {
          paths = cleanOutputs;
          priority = drv.meta.priority or lib.meta.defaultPriority;
        };
    in
    paths: outputs: map (pickOutputsOne outputs) paths;

  paths =
    let
      basePaths = [
        etcProfile
        # ldconfig wrapper must come first so it overrides the original ldconfig
        ldconfig
        # magic package that just creates a directory, to ensure that
        # the entire directory can't be a symlink, as we will write
        # compiled schemas to it
        ensureGsettingsSchemasIsDirectory
      ]
      ++ baseTargetPaths
      ++ targetPaths;
    in
    pickOutputs basePaths [
      "out"
      "lib"
      "bin"
    ];

  paths32 = lib.optionals isMultiBuild (
    let
      basePaths = baseMultiPaths ++ multiPaths;
    in
    pickOutputs basePaths [
      "out"
      "lib"
    ]
  );

  allPaths = paths ++ paths32;

  rootfs-builder = pkgs.buildPackages.rustPlatform.buildRustPackage {
    name = "fhs-rootfs-bulder";
    src = ./rootfs-builder;
    cargoLock.lockFile = ./rootfs-builder/Cargo.lock;
    doCheck = false;
  };

  rootfs =
    pkgs.runCommand "${name}-fhsenv-rootfs"
      {
        __structuredAttrs = true;
        exportReferencesGraph.graph = lib.concatMap (p: p.paths) allPaths;
        inherit
          paths
          paths32
          isMultiBuild
          includeClosures
          ;
        nativeBuildInputs = [ pkgs.jq ];
      }
      ''
        ${rootfs-builder}/bin/rootfs-builder

        # create a bunch of symlinks for usrmerge
        ln -s /usr/bin $out/bin
        ln -s /usr/sbin $out/sbin
        ln -s /usr/lib $out/lib
        ln -s /usr/lib32 $out/lib32
        ln -s /usr/lib64 $out/lib64
        ln -s /usr/${defaultLib} $out/usr/lib
        ln -s /usr/libexec $out/libexec

        # symlink 32-bit ld-linux so it's visible in /lib
        if [ -e $out/usr/lib32/ld-linux.so.2 ]; then
          ln -s /usr/lib32/ld-linux.so.2 $out/usr/lib64/ld-linux.so.2
        fi

        # symlink /etc/mtab -> /proc/mounts (compat for old userspace progs)
        ln -s /proc/mounts $out/etc/mtab

        if [[ -d $out/usr/share/gsettings-schemas/ ]]; then
          for d in $out/usr/share/gsettings-schemas/*; do
            # Force symlink, in case there are duplicates
            ln -fsr $d/glib-2.0/schemas/*.xml $out/usr/share/glib-2.0/schemas
            ln -fsr $d/glib-2.0/schemas/*.gschema.override $out/usr/share/glib-2.0/schemas
          done
          ${pkgs.pkgsBuildBuild.glib.dev}/bin/glib-compile-schemas $out/usr/share/glib-2.0/schemas
        fi

        ${extraBuildCommands}
        ${lib.optionalString isMultiBuild extraBuildCommandsMulti}
      '';
in
rootfs