|
| 1 | +# (ThomasK33): Inlined the relevant dockerTools functions, so that we can |
| 2 | +# set the maxLayers attribute on the attribute set passed |
| 3 | +# to the buildNixShellImage function. |
| 4 | +# |
| 5 | +# I'll create an upstream PR to nixpkgs with those changes, making this |
| 6 | +# eventually unnecessary and ripe for removal. |
| 7 | +{ |
| 8 | + lib, |
| 9 | + dockerTools, |
| 10 | + devShellTools, |
| 11 | + bashInteractive, |
| 12 | + fakeNss, |
| 13 | + runCommand, |
| 14 | + writeShellScriptBin, |
| 15 | + writeText, |
| 16 | + cacert, |
| 17 | + storeDir ? builtins.storeDir, |
| 18 | + pigz, |
| 19 | + zstd, |
| 20 | +}: |
| 21 | +let |
| 22 | + inherit (lib) |
| 23 | + optionalString |
| 24 | + ; |
| 25 | + |
| 26 | + inherit (devShellTools) |
| 27 | + valueToString |
| 28 | + ; |
| 29 | + |
| 30 | + inherit (dockerTools) |
| 31 | + streamLayeredImage |
| 32 | + binSh |
| 33 | + usrBinEnv |
| 34 | + ; |
| 35 | + |
| 36 | + compressors = { |
| 37 | + none = { |
| 38 | + ext = ""; |
| 39 | + nativeInputs = [ ]; |
| 40 | + compress = "cat"; |
| 41 | + decompress = "cat"; |
| 42 | + }; |
| 43 | + gz = { |
| 44 | + ext = ".gz"; |
| 45 | + nativeInputs = [ pigz ]; |
| 46 | + compress = "pigz -p$NIX_BUILD_CORES -nTR"; |
| 47 | + decompress = "pigz -d -p$NIX_BUILD_CORES"; |
| 48 | + }; |
| 49 | + zstd = { |
| 50 | + ext = ".zst"; |
| 51 | + nativeInputs = [ zstd ]; |
| 52 | + compress = "zstd -T$NIX_BUILD_CORES"; |
| 53 | + decompress = "zstd -d -T$NIX_BUILD_CORES"; |
| 54 | + }; |
| 55 | + }; |
| 56 | + compressorForImage = |
| 57 | + compressor: imageName: |
| 58 | + compressors.${compressor} |
| 59 | + or (throw "in docker image ${imageName}: compressor must be one of: [${toString builtins.attrNames compressors}]"); |
| 60 | + |
| 61 | + streamNixShellImage = |
| 62 | + { |
| 63 | + drv, |
| 64 | + name ? drv.name + "-env", |
| 65 | + tag ? null, |
| 66 | + uid ? 1000, |
| 67 | + gid ? 1000, |
| 68 | + homeDirectory ? "/build", |
| 69 | + shell ? bashInteractive + "/bin/bash", |
| 70 | + command ? null, |
| 71 | + run ? null, |
| 72 | + maxLayers ? 100, |
| 73 | + }: |
| 74 | + assert lib.assertMsg (!(drv.drvAttrs.__structuredAttrs or false)) |
| 75 | + "streamNixShellImage: Does not work with the derivation ${drv.name} because it uses __structuredAttrs"; |
| 76 | + assert lib.assertMsg ( |
| 77 | + command == null || run == null |
| 78 | + ) "streamNixShellImage: Can't specify both command and run"; |
| 79 | + let |
| 80 | + |
| 81 | + # A binary that calls the command to build the derivation |
| 82 | + builder = writeShellScriptBin "buildDerivation" '' |
| 83 | + exec ${lib.escapeShellArg (valueToString drv.drvAttrs.builder)} ${lib.escapeShellArgs (map valueToString drv.drvAttrs.args)} |
| 84 | + ''; |
| 85 | + |
| 86 | + staticPath = "${dirOf shell}:${lib.makeBinPath [ builder ]}"; |
| 87 | + |
| 88 | + # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L493-L526 |
| 89 | + rcfile = writeText "nix-shell-rc" '' |
| 90 | + unset PATH |
| 91 | + dontAddDisableDepTrack=1 |
| 92 | + # TODO: https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L506 |
| 93 | + [ -e $stdenv/setup ] && source $stdenv/setup |
| 94 | + PATH=${staticPath}:"$PATH" |
| 95 | + SHELL=${lib.escapeShellArg shell} |
| 96 | + BASH=${lib.escapeShellArg shell} |
| 97 | + set +e |
| 98 | + [ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] ' |
| 99 | + if [ "$(type -t runHook)" = function ]; then |
| 100 | + runHook shellHook |
| 101 | + fi |
| 102 | + unset NIX_ENFORCE_PURITY |
| 103 | + shopt -u nullglob |
| 104 | + shopt -s execfail |
| 105 | + ${optionalString (command != null || run != null) '' |
| 106 | + ${optionalString (command != null) command} |
| 107 | + ${optionalString (run != null) run} |
| 108 | + exit |
| 109 | + ''} |
| 110 | + ''; |
| 111 | + |
| 112 | + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/globals.hh#L464-L465 |
| 113 | + sandboxBuildDir = "/build"; |
| 114 | + |
| 115 | + drvEnv = |
| 116 | + devShellTools.unstructuredDerivationInputEnv { inherit (drv) drvAttrs; } |
| 117 | + // devShellTools.derivationOutputEnv { |
| 118 | + outputList = drv.outputs; |
| 119 | + outputMap = drv; |
| 120 | + }; |
| 121 | + |
| 122 | + # Environment variables set in the image |
| 123 | + envVars = |
| 124 | + { |
| 125 | + |
| 126 | + # Root certificates for internet access |
| 127 | + SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt"; |
| 128 | + NIX_SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt"; |
| 129 | + |
| 130 | + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1027-L1030 |
| 131 | + # PATH = "/path-not-set"; |
| 132 | + # Allows calling bash and `buildDerivation` as the Cmd |
| 133 | + PATH = staticPath; |
| 134 | + |
| 135 | + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1032-L1038 |
| 136 | + HOME = homeDirectory; |
| 137 | + |
| 138 | + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1040-L1044 |
| 139 | + NIX_STORE = storeDir; |
| 140 | + |
| 141 | + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1046-L1047 |
| 142 | + # TODO: Make configurable? |
| 143 | + NIX_BUILD_CORES = "1"; |
| 144 | + |
| 145 | + } |
| 146 | + // drvEnv |
| 147 | + // { |
| 148 | + |
| 149 | + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1008-L1010 |
| 150 | + NIX_BUILD_TOP = sandboxBuildDir; |
| 151 | + |
| 152 | + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1012-L1013 |
| 153 | + TMPDIR = sandboxBuildDir; |
| 154 | + TEMPDIR = sandboxBuildDir; |
| 155 | + TMP = sandboxBuildDir; |
| 156 | + TEMP = sandboxBuildDir; |
| 157 | + |
| 158 | + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1015-L1019 |
| 159 | + PWD = sandboxBuildDir; |
| 160 | + |
| 161 | + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1071-L1074 |
| 162 | + # We don't set it here because the output here isn't handled in any special way |
| 163 | + # NIX_LOG_FD = "2"; |
| 164 | + |
| 165 | + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1076-L1077 |
| 166 | + TERM = "xterm-256color"; |
| 167 | + }; |
| 168 | + |
| 169 | + in |
| 170 | + streamLayeredImage { |
| 171 | + inherit name tag maxLayers; |
| 172 | + contents = [ |
| 173 | + binSh |
| 174 | + usrBinEnv |
| 175 | + (fakeNss.override { |
| 176 | + # Allows programs to look up the build user's home directory |
| 177 | + # https://github.com/NixOS/nix/blob/ffe155abd36366a870482625543f9bf924a58281/src/libstore/build/local-derivation-goal.cc#L906-L910 |
| 178 | + # Slightly differs however: We use the passed-in homeDirectory instead of sandboxBuildDir. |
| 179 | + # We're doing this because it's arguably a bug in Nix that sandboxBuildDir is used here: https://github.com/NixOS/nix/issues/6379 |
| 180 | + extraPasswdLines = [ |
| 181 | + "nixbld:x:${toString uid}:${toString gid}:Build user:${homeDirectory}:/noshell" |
| 182 | + ]; |
| 183 | + extraGroupLines = [ |
| 184 | + "nixbld:!:${toString gid}:" |
| 185 | + ]; |
| 186 | + }) |
| 187 | + ]; |
| 188 | + |
| 189 | + fakeRootCommands = '' |
| 190 | + # Effectively a single-user installation of Nix, giving the user full |
| 191 | + # control over the Nix store. Needed for building the derivation this |
| 192 | + # shell is for, but also in case one wants to use Nix inside the |
| 193 | + # image |
| 194 | + mkdir -p ./nix/{store,var/nix} ./etc/nix |
| 195 | + chown -R ${toString uid}:${toString gid} ./nix ./etc/nix |
| 196 | +
|
| 197 | + # Gives the user control over the build directory |
| 198 | + mkdir -p .${sandboxBuildDir} |
| 199 | + chown -R ${toString uid}:${toString gid} .${sandboxBuildDir} |
| 200 | + ''; |
| 201 | + |
| 202 | + # Run this image as the given uid/gid |
| 203 | + config.User = "${toString uid}:${toString gid}"; |
| 204 | + config.Cmd = |
| 205 | + # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L185-L186 |
| 206 | + # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L534-L536 |
| 207 | + if run == null then |
| 208 | + [ |
| 209 | + shell |
| 210 | + "--rcfile" |
| 211 | + rcfile |
| 212 | + ] |
| 213 | + else |
| 214 | + [ |
| 215 | + shell |
| 216 | + rcfile |
| 217 | + ]; |
| 218 | + config.WorkingDir = sandboxBuildDir; |
| 219 | + config.Env = lib.mapAttrsToList (name: value: "${name}=${value}") envVars; |
| 220 | + }; |
| 221 | +in |
| 222 | +{ |
| 223 | + |
| 224 | + # This function streams a docker image that behaves like a nix-shell for a derivation |
| 225 | + # Docs: doc/build-helpers/images/dockertools.section.md |
| 226 | + # Tests: nixos/tests/docker-tools-nix-shell.nix |
| 227 | + |
| 228 | + # Wrapper around streamNixShellImage to build an image from the result |
| 229 | + # Docs: doc/build-helpers/images/dockertools.section.md |
| 230 | + # Tests: nixos/tests/docker-tools-nix-shell.nix |
| 231 | + buildNixShellImage = |
| 232 | + { |
| 233 | + drv, |
| 234 | + compressor ? "gz", |
| 235 | + ... |
| 236 | + }@args: |
| 237 | + let |
| 238 | + stream = streamNixShellImage (builtins.removeAttrs args [ "compressor" ]); |
| 239 | + compress = compressorForImage compressor drv.name; |
| 240 | + in |
| 241 | + runCommand "${drv.name}-env.tar${compress.ext}" { |
| 242 | + inherit (stream) imageName; |
| 243 | + passthru = { inherit (stream) imageTag; }; |
| 244 | + nativeBuildInputs = compress.nativeInputs; |
| 245 | + } "${stream} | ${compress.compress} > $out"; |
| 246 | +} |
0 commit comments