about summary refs log tree commit diff
path: root/pkgs/build-support/build-fhsenv-chroot/env.nix
blob: 6a82435d7067c0d49eb120c752248a9d80416994 (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
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
{ stdenv, lib, buildEnv, writeText, pkgs, pkgsi686Linux }:

{ name
, profile ? ""
, targetPkgs ? pkgs: []
, multiPkgs ? pkgs: []
, extraBuildCommands ? ""
, extraBuildCommandsMulti ? ""
, extraOutputsToInstall ? []
}:

# 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 /lib32

let
  is64Bit = stdenv.hostPlatform.parsed.cpu.bits == 64;
  # multi-lib glibc is only supported on x86_64
  isMultiBuild  = multiPkgs != null && stdenv.hostPlatform.system == "x86_64-linux";
  isTargetBuild = !isMultiBuild;

  # list of packages (usually programs) which are only be installed for the
  # host's architecture
  targetPaths = targetPkgs pkgs ++ (if multiPkgs == null then [] else multiPkgs pkgs);

  # list of packages which are installed for both x86 and x86_64 on x86_64
  # systems
  multiPaths = multiPkgs pkgsi686Linux;

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

  etcProfile = writeText "profile" ''
    export PS1='${name}-chrootenv:\u@\h:\w\$ '
    export LOCALE_ARCHIVE='/usr/lib/locale/locale-archive'
    export LD_LIBRARY_PATH="/run/opengl-driver/lib:/run/opengl-driver-32/lib:/usr/lib:/usr/lib32''${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH"
    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/basedir-spec-latest.html>
    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'
    export NIX_CFLAGS_LINK='-L/usr/lib -L/usr/lib32'
    export NIX_LDFLAGS='-L/usr/lib -L/usr/lib32'
    export PKG_CONFIG_PATH=/usr/lib/pkgconfig
    export ACLOCAL_PATH=/usr/share/aclocal

    ${profile}
  '';

  # Compose /etc for the chroot environment
  etcPkg = stdenv.mkDerivation {
    name         = "${name}-chrootenv-etc";
    buildCommand = ''
      mkdir -p $out/etc
      cd $out/etc

      # environment variables
      ln -s ${etcProfile} profile

      # compatibility with NixOS
      ln -s /host/etc/static static

      # symlink nix config
      ln -s /host/etc/nix nix

      # symlink some NSS stuff
      ln -s /host/etc/passwd passwd
      ln -s /host/etc/group group
      ln -s /host/etc/shadow shadow
      ln -s /host/etc/hosts hosts
      ln -s /host/etc/resolv.conf resolv.conf
      ln -s /host/etc/nsswitch.conf nsswitch.conf

      # symlink user profiles
      ln -s /host/etc/profiles profiles

      # symlink sudo and su stuff
      ln -s /host/etc/login.defs login.defs
      ln -s /host/etc/sudoers sudoers
      ln -s /host/etc/sudoers.d sudoers.d

      # symlink other core stuff
      ln -s /host/etc/localtime localtime
      ln -s /host/etc/zoneinfo zoneinfo
      ln -s /host/etc/machine-id machine-id
      ln -s /host/etc/os-release os-release

      # symlink PAM stuff
      ln -s /host/etc/pam.d pam.d

      # symlink fonts stuff
      ln -s /host/etc/fonts fonts

      # symlink ALSA stuff
      ln -s /host/etc/asound.conf asound.conf
      ln -s /host/etc/alsa alsa

      # symlink SSL certs
      mkdir -p ssl
      ln -s /host/etc/ssl/certs ssl/certs

      # symlink /etc/mtab -> /proc/mounts (compat for old userspace progs)
      ln -s /proc/mounts mtab
    '';
  };

  # Composes a /usr-like directory structure
  staticUsrProfileTarget = buildEnv {
    name = "${name}-usr-target";
    paths = [ etcPkg ] ++ basePkgs ++ targetPaths;
    extraOutputsToInstall = [ "out" "lib" "bin" ] ++ extraOutputsToInstall;
    ignoreCollisions = true;
    postBuild = ''
      if [[ -d  $out/share/gsettings-schemas/ ]]; then
          # Recreate the standard schemas directory if its a symlink to make it writable
          if [[ -L $out/share/glib-2.0 ]]; then
              target=$(readlink $out/share/glib-2.0)
              rm $out/share/glib-2.0
              mkdir $out/share/glib-2.0
              ln -fs $target/* $out/share/glib-2.0
          fi

          if [[ -L $out/share/glib-2.0/schemas ]]; then
              target=$(readlink $out/share/glib-2.0/schemas)
              rm $out/share/glib-2.0/schemas
              mkdir $out/share/glib-2.0/schemas
              ln -fs $target/* $out/share/glib-2.0/schemas
          fi

          mkdir -p $out/share/glib-2.0/schemas

          for d in $out/share/gsettings-schemas/*; do
              # Force symlink, in case there are duplicates
              ln -fs $d/glib-2.0/schemas/*.xml $out/share/glib-2.0/schemas
              ln -fs $d/glib-2.0/schemas/*.gschema.override $out/share/glib-2.0/schemas
          done

          # and compile them
          ${pkgs.glib.dev}/bin/glib-compile-schemas $out/share/glib-2.0/schemas
      fi
    '';
  };

  staticUsrProfileMulti = buildEnv {
    name = "${name}-usr-multi";
    paths = baseMultiPkgs ++ multiPaths;
    extraOutputsToInstall = [ "out" "lib" ] ++ extraOutputsToInstall;
    ignoreCollisions = true;
  };

  # setup library paths only for the targeted architecture
  setupLibDirs_target = ''
    # link content of targetPaths
    cp -rsHf ${staticUsrProfileTarget}/lib lib
    ln -s lib lib${if is64Bit then "64" else "32"}
  '';

  # setup /lib, /lib32 and /lib64
  setupLibDirs_multi = ''
    mkdir -m0755 lib32
    mkdir -m0755 lib64
    ln -s lib64 lib

    # copy glibc stuff
    cp -rsHf ${staticUsrProfileTarget}/lib/32/* lib32/ && chmod u+w -R lib32/

    # copy content of multiPaths (32bit libs)
    [ -d ${staticUsrProfileMulti}/lib ] && cp -rsHf ${staticUsrProfileMulti}/lib/* lib32/ && chmod u+w -R lib32/

    # copy content of targetPaths (64bit libs)
    cp -rsHf ${staticUsrProfileTarget}/lib/* lib64/ && chmod u+w -R lib64/

    # symlink 32-bit ld-linux.so
    ln -Ls ${staticUsrProfileTarget}/lib/32/ld-linux.so.2 lib/
  '';

  setupLibDirs = if isTargetBuild then setupLibDirs_target
                                  else setupLibDirs_multi;

  # the target profile is the actual profile that will be used for the chroot
  setupTargetProfile = ''
    mkdir -m0755 usr
    cd usr
    ${setupLibDirs}
    for i in bin sbin share include; do
      if [ -d "${staticUsrProfileTarget}/$i" ]; then
        cp -rsHf "${staticUsrProfileTarget}/$i" "$i"
      fi
    done
    cd ..

    for i in var etc opt; do
      if [ -d "${staticUsrProfileTarget}/$i" ]; then
        cp -rsHf "${staticUsrProfileTarget}/$i" "$i"
      fi
    done
    for i in usr/{bin,sbin,lib,lib32,lib64}; do
      if [ -d "$i" ]; then
        ln -s "$i"
      fi
    done
  '';

in stdenv.mkDerivation {
  name         = "${name}-fhs";
  buildCommand = ''
    mkdir -p $out
    cd $out
    ${setupTargetProfile}
    cd $out
    ${extraBuildCommands}
    cd $out
    ${lib.optionalString isMultiBuild extraBuildCommandsMulti}
  '';
  preferLocalBuild = true;
  allowSubstitutes = false;
}