diff options
author | Silvan Mosberger <github@infinisil.com> | 2022-11-26 20:21:59 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-26 20:21:59 +0100 |
commit | a566d0842e917eb5b80e2ad221dc7658faa624bf (patch) | |
tree | 9918c24c191a5a8a865538bcacef12c465b9730b /pkgs/build-support/docker | |
parent | 50e0b80104640d23899bd09ac200a007aa8fd198 (diff) | |
parent | a1cf24939404560acaac6555ae55942ae4163b9f (diff) | |
download | nixpkgs-a566d0842e917eb5b80e2ad221dc7658faa624bf.tar nixpkgs-a566d0842e917eb5b80e2ad221dc7658faa624bf.tar.gz nixpkgs-a566d0842e917eb5b80e2ad221dc7658faa624bf.tar.bz2 nixpkgs-a566d0842e917eb5b80e2ad221dc7658faa624bf.tar.lz nixpkgs-a566d0842e917eb5b80e2ad221dc7658faa624bf.tar.xz nixpkgs-a566d0842e917eb5b80e2ad221dc7658faa624bf.tar.zst nixpkgs-a566d0842e917eb5b80e2ad221dc7658faa624bf.zip |
Merge pull request #172736 from infinisil/docker-nix-shell
Diffstat (limited to 'pkgs/build-support/docker')
-rw-r--r-- | pkgs/build-support/docker/default.nix | 188 | ||||
-rw-r--r-- | pkgs/build-support/docker/examples.nix | 116 |
2 files changed, 302 insertions, 2 deletions
diff --git a/pkgs/build-support/docker/default.nix b/pkgs/build-support/docker/default.nix index 6b07865928e..9a0d01acaae 100644 --- a/pkgs/build-support/docker/default.nix +++ b/pkgs/build-support/docker/default.nix @@ -19,6 +19,7 @@ , pigz , rsync , runCommand +, runCommandNoCC , runtimeShell , shadow , skopeo @@ -30,6 +31,7 @@ , vmTools , writeReferencesToFile , writeScript +, writeShellScriptBin , writeText , writeTextDir , writePython3 @@ -78,7 +80,7 @@ let in rec { examples = callPackage ./examples.nix { - inherit buildImage buildLayeredImage fakeNss pullImage shadowSetup buildImageWithNixDb; + inherit buildImage buildLayeredImage fakeNss pullImage shadowSetup buildImageWithNixDb streamNixShellImage; }; tests = { @@ -1034,4 +1036,188 @@ rec { ''; in result; + + # This function streams a docker image that behaves like a nix-shell for a derivation + streamNixShellImage = + { # The derivation whose environment this docker image should be based on + drv + , # Image Name + name ? drv.name + "-env" + , # Image tag, the Nix's output hash will be used if null + tag ? null + , # User id to run the container as. Defaults to 1000, because many + # binaries don't like to be run as root + uid ? 1000 + , # Group id to run the container as, see also uid + gid ? 1000 + , # The home directory of the user + homeDirectory ? "/build" + , # The path to the bash binary to use as the shell. See `NIX_BUILD_SHELL` in `man nix-shell` + shell ? bashInteractive + "/bin/bash" + , # Run this command in the environment of the derivation, in an interactive shell. See `--command` in `man nix-shell` + command ? null + , # Same as `command`, but runs the command in a non-interactive shell instead. See `--run` in `man nix-shell` + run ? null + }: + assert lib.assertMsg (! (drv.drvAttrs.__structuredAttrs or false)) + "streamNixShellImage: Does not work with the derivation ${drv.name} because it uses __structuredAttrs"; + assert lib.assertMsg (command == null || run == null) + "streamNixShellImage: Can't specify both command and run"; + let + + # A binary that calls the command to build the derivation + builder = writeShellScriptBin "buildDerivation" '' + exec ${lib.escapeShellArg (stringValue drv.drvAttrs.builder)} ${lib.escapeShellArgs (map stringValue drv.drvAttrs.args)} + ''; + + staticPath = "${dirOf shell}:${lib.makeBinPath [ builder ]}"; + + # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L493-L526 + rcfile = writeText "nix-shell-rc" '' + unset PATH + dontAddDisableDepTrack=1 + # TODO: https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L506 + [ -e $stdenv/setup ] && source $stdenv/setup + PATH=${staticPath}:"$PATH" + SHELL=${lib.escapeShellArg shell} + BASH=${lib.escapeShellArg shell} + set +e + [ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] ' + if [ "$(type -t runHook)" = function ]; then + runHook shellHook + fi + unset NIX_ENFORCE_PURITY + shopt -u nullglob + shopt -s execfail + ${optionalString (command != null || run != null) '' + ${optionalString (command != null) command} + ${optionalString (run != null) run} + exit + ''} + ''; + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/globals.hh#L464-L465 + sandboxBuildDir = "/build"; + + # This function closely mirrors what this Nix code does: + # https://github.com/NixOS/nix/blob/2.8.0/src/libexpr/primops.cc#L1102 + # https://github.com/NixOS/nix/blob/2.8.0/src/libexpr/eval.cc#L1981-L2036 + stringValue = value: + # We can't just use `toString` on all derivation attributes because that + # would not put path literals in the closure. So we explicitly copy + # those into the store here + if builtins.typeOf value == "path" then "${value}" + else if builtins.typeOf value == "list" then toString (map stringValue value) + else toString value; + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L992-L1004 + drvEnv = lib.mapAttrs' (name: value: + let str = stringValue value; + in if lib.elem name (drv.drvAttrs.passAsFile or []) + then lib.nameValuePair "${name}Path" (writeText "pass-as-text-${name}" str) + else lib.nameValuePair name str + ) drv.drvAttrs // + # A mapping from output name to the nix store path where they should end up + # https://github.com/NixOS/nix/blob/2.8.0/src/libexpr/primops.cc#L1253 + lib.genAttrs drv.outputs (output: builtins.unsafeDiscardStringContext drv.${output}.outPath); + + # Environment variables set in the image + envVars = { + + # Root certificates for internet access + SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt"; + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1027-L1030 + # PATH = "/path-not-set"; + # Allows calling bash and `buildDerivation` as the Cmd + PATH = staticPath; + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1032-L1038 + HOME = homeDirectory; + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1040-L1044 + NIX_STORE = storeDir; + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1046-L1047 + # TODO: Make configurable? + NIX_BUILD_CORES = "1"; + + } // drvEnv // { + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1008-L1010 + NIX_BUILD_TOP = sandboxBuildDir; + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1012-L1013 + TMPDIR = sandboxBuildDir; + TEMPDIR = sandboxBuildDir; + TMP = sandboxBuildDir; + TEMP = sandboxBuildDir; + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1015-L1019 + PWD = sandboxBuildDir; + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1071-L1074 + # We don't set it here because the output here isn't handled in any special way + # NIX_LOG_FD = "2"; + + # https://github.com/NixOS/nix/blob/2.8.0/src/libstore/build/local-derivation-goal.cc#L1076-L1077 + TERM = "xterm-256color"; + }; + + + in streamLayeredImage { + inherit name tag; + contents = [ + binSh + usrBinEnv + (fakeNss.override { + # Allows programs to look up the build user's home directory + # https://github.com/NixOS/nix/blob/ffe155abd36366a870482625543f9bf924a58281/src/libstore/build/local-derivation-goal.cc#L906-L910 + # Slightly differs however: We use the passed-in homeDirectory instead of sandboxBuildDir. + # We're doing this because it's arguably a bug in Nix that sandboxBuildDir is used here: https://github.com/NixOS/nix/issues/6379 + extraPasswdLines = [ + "nixbld:x:${toString uid}:${toString gid}:Build user:${homeDirectory}:/noshell" + ]; + extraGroupLines = [ + "nixbld:!:${toString gid}:" + ]; + }) + ]; + + fakeRootCommands = '' + # Effectively a single-user installation of Nix, giving the user full + # control over the Nix store. Needed for building the derivation this + # shell is for, but also in case one wants to use Nix inside the + # image + mkdir -p ./nix/{store,var/nix} ./etc/nix + chown -R ${toString uid}:${toString gid} ./nix ./etc/nix + + # Gives the user control over the build directory + mkdir -p .${sandboxBuildDir} + chown -R ${toString uid}:${toString gid} .${sandboxBuildDir} + ''; + + # Run this image as the given uid/gid + config.User = "${toString uid}:${toString gid}"; + config.Cmd = + # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L185-L186 + # https://github.com/NixOS/nix/blob/2.8.0/src/nix-build/nix-build.cc#L534-L536 + if run == null + then [ shell "--rcfile" rcfile ] + else [ shell rcfile ]; + config.WorkingDir = sandboxBuildDir; + config.Env = lib.mapAttrsToList (name: value: "${name}=${value}") envVars; + }; + + # Wrapper around streamNixShellImage to build an image from the result + buildNixShellImage = { drv, ... }@args: + let + stream = streamNixShellImage args; + in + runCommand "${drv.name}-env.tar.gz" + { + inherit (stream) imageName; + passthru = { inherit (stream) imageTag; }; + nativeBuildInputs = [ pigz ]; + } "${stream} | pigz -nT > $out"; } diff --git a/pkgs/build-support/docker/examples.nix b/pkgs/build-support/docker/examples.nix index 1e9f07045e3..802b2f79f0f 100644 --- a/pkgs/build-support/docker/examples.nix +++ b/pkgs/build-support/docker/examples.nix @@ -7,7 +7,7 @@ # $ nix-build '<nixpkgs>' -A dockerTools.examples.redis # $ docker load < result -{ pkgs, buildImage, buildLayeredImage, fakeNss, pullImage, shadowSetup, buildImageWithNixDb, pkgsCross }: +{ pkgs, buildImage, buildLayeredImage, fakeNss, pullImage, shadowSetup, buildImageWithNixDb, pkgsCross, streamNixShellImage }: let nixosLib = import ../../../nixos/lib { @@ -715,4 +715,118 @@ rec { config = { }; }; + + nix-shell-basic = streamNixShellImage { + name = "nix-shell-basic"; + tag = "latest"; + drv = pkgs.hello; + }; + + nix-shell-hook = streamNixShellImage { + name = "nix-shell-hook"; + tag = "latest"; + drv = pkgs.mkShell { + shellHook = '' + echo "This is the shell hook!" + exit + ''; + }; + }; + + nix-shell-inputs = streamNixShellImage { + name = "nix-shell-inputs"; + tag = "latest"; + drv = pkgs.mkShell { + nativeBuildInputs = [ + pkgs.hello + ]; + }; + command = '' + hello + ''; + }; + + nix-shell-pass-as-file = streamNixShellImage { + name = "nix-shell-pass-as-file"; + tag = "latest"; + drv = pkgs.mkShell { + str = "this is a string"; + passAsFile = [ "str" ]; + }; + command = '' + cat "$strPath" + ''; + }; + + nix-shell-run = streamNixShellImage { + name = "nix-shell-run"; + tag = "latest"; + drv = pkgs.mkShell {}; + run = '' + case "$-" in + *i*) echo This shell is interactive ;; + *) echo This shell is not interactive ;; + esac + ''; + }; + + nix-shell-command = streamNixShellImage { + name = "nix-shell-command"; + tag = "latest"; + drv = pkgs.mkShell {}; + command = '' + case "$-" in + *i*) echo This shell is interactive ;; + *) echo This shell is not interactive ;; + esac + ''; + }; + + nix-shell-writable-home = streamNixShellImage { + name = "nix-shell-writable-home"; + tag = "latest"; + drv = pkgs.mkShell {}; + run = '' + if [[ "$HOME" != "$(eval "echo ~$(whoami)")" ]]; then + echo "\$HOME ($HOME) is not the same as ~\$(whoami) ($(eval "echo ~$(whoami)"))" + exit 1 + fi + + if ! touch $HOME/test-file; then + echo "home directory is not writable" + exit 1 + fi + echo "home directory is writable" + ''; + }; + + nix-shell-nonexistent-home = streamNixShellImage { + name = "nix-shell-nonexistent-home"; + tag = "latest"; + drv = pkgs.mkShell {}; + homeDirectory = "/homeless-shelter"; + run = '' + if [[ "$HOME" != "$(eval "echo ~$(whoami)")" ]]; then + echo "\$HOME ($HOME) is not the same as ~\$(whoami) ($(eval "echo ~$(whoami)"))" + exit 1 + fi + + if -e $HOME; then + echo "home directory exists" + exit 1 + fi + echo "home directory doesn't exist" + ''; + }; + + nix-shell-build-derivation = streamNixShellImage { + name = "nix-shell-build-derivation"; + tag = "latest"; + drv = pkgs.hello; + run = '' + buildDerivation + $out/bin/hello + ''; + }; + } |