summary refs log tree commit diff
diff options
context:
space:
mode:
authorSilvan Mosberger <contact@infinisil.com>2022-05-09 20:43:52 +0200
committerSilvan Mosberger <contact@infinisil.com>2022-10-07 22:04:22 +0200
commit8ec0837a72b532df84d61b3f8571793f15326b29 (patch)
treeab2577d8f8599efdb062d5e04e1adb4d19b7fd52
parent79ae4eb9979bf89a6d5e6f0804a216b3b86106a5 (diff)
downloadnixpkgs-8ec0837a72b532df84d61b3f8571793f15326b29.tar
nixpkgs-8ec0837a72b532df84d61b3f8571793f15326b29.tar.gz
nixpkgs-8ec0837a72b532df84d61b3f8571793f15326b29.tar.bz2
nixpkgs-8ec0837a72b532df84d61b3f8571793f15326b29.tar.lz
nixpkgs-8ec0837a72b532df84d61b3f8571793f15326b29.tar.xz
nixpkgs-8ec0837a72b532df84d61b3f8571793f15326b29.tar.zst
nixpkgs-8ec0837a72b532df84d61b3f8571793f15326b29.zip
Introduce dockerTools.buildNixShellImage
-rw-r--r--doc/builders/images/dockertools.section.md139
-rw-r--r--pkgs/build-support/docker/default.nix183
2 files changed, 322 insertions, 0 deletions
diff --git a/doc/builders/images/dockertools.section.md b/doc/builders/images/dockertools.section.md
index db1a2a214d1..581bffd1a5a 100644
--- a/doc/builders/images/dockertools.section.md
+++ b/doc/builders/images/dockertools.section.md
@@ -394,3 +394,142 @@ buildImage {
   };
 }
 ```
+
+## buildNixShellImage {#ssec-pkgs-dockerTools-buildNixShellImage}
+
+Create a Docker image that sets up an environment similar to that of running `nix-shell` on a derivation.
+When run in Docker, this environment somewhat resembles the Nix sandbox typically used by `nix-build`, with a major difference being that access to the internet is allowed.
+It additionally also behaves like an interactive `nix-shell`, running things like `shellHook` and setting an interactive prompt.
+If the derivation is fully buildable (i.e. `nix-build` can be used on it), running `buildDerivation` inside such a Docker image will build the derivation, with all its outputs being available in the correct `/nix/store` paths, pointed to by the respective environment variables like `$out`, etc.
+
+::: {.warning}
+The behavior doesn't match `nix-shell` or `nix-build` exactly and this function is known not to work correctly for e.g. fixed-output derivations, content-addressed derivations, impure derivations and other special types of derivations.
+:::
+
+### Arguments
+
+`drv`
+
+: The derivation on which to base the Docker image.
+
+    Adding packages to the Docker image is possible by e.g. extending the list of `nativeBuildInputs` of this derivation like
+
+    ```nix
+    buildNixShellImage {
+      drv = someDrv.overrideAttrs (old: {
+        nativeBuildInputs = old.nativeBuildInputs or [] ++ [
+          somethingExtra
+        ];
+      });
+      # ...
+    }
+    ```
+
+    Similarly, you can extend the image initialization script by extending `shellHook`
+
+`name` _optional_
+
+: The name of the resulting image.
+
+    *Default:* `drv.name + "-env"`
+
+`tag` _optional_
+
+: Tag of the generated image.
+
+    *Default:* the resulting image derivation output path's hash
+
+`uid`/`gid` _optional_
+
+: The user/group ID to run the container as. This is like a `nixbld` build user.
+
+    *Default:* 1000/1000
+
+`homeDirectory` _optional_
+
+: The home directory of the user the container is running as
+
+    *Default:* `/build`
+
+`shell` _optional_
+
+: The path to the `bash` binary to use as the shell. This shell is started when running the image.
+
+    *Default:* `pkgs.bashInteractive + "/bin/bash"`
+
+`command` _optional_
+
+: Run this command in the environment of the derivation, in an interactive shell. See the `--command` option in the [`nix-shell` documentation](https://nixos.org/manual/nix/stable/command-ref/nix-shell.html?highlight=nix-shell#options).
+
+    *Default:* (none)
+
+`run` _optional_
+
+: Same as `command`, but runs the command in a non-interactive shell instead. See the `--run` option in the [`nix-shell` documentation](https://nixos.org/manual/nix/stable/command-ref/nix-shell.html?highlight=nix-shell#options).
+
+    *Default:* (none)
+
+### Example
+
+The following shows how to build the `pkgs.hello` package inside a Docker container built with `buildNixShellImage`.
+
+```nix
+with import <nixpkgs> {};
+dockerTools.buildNixShellImage {
+  drv = hello;
+}
+```
+
+Build the derivation:
+
+```console
+nix-build hello.nix
+```
+
+    these 8 derivations will be built:
+      /nix/store/xmw3a5ln29rdalavcxk1w3m4zb2n7kk6-nix-shell-rc.drv
+    ...
+    Creating layer 56 from paths: ['/nix/store/crpnj8ssz0va2q0p5ibv9i6k6n52gcya-stdenv-linux']
+    Creating layer 57 with customisation...
+    Adding manifests...
+    Done.
+    /nix/store/cpyn1lc897ghx0rhr2xy49jvyn52bazv-hello-2.12-env.tar.gz
+
+Load the image:
+
+```console
+docker load -i result
+```
+
+    0d9f4c4cd109: Loading layer [==================================================>]   2.56MB/2.56MB
+    ...
+    ab1d897c0697: Loading layer [==================================================>]  10.24kB/10.24kB
+    Loaded image: hello-2.12-env:pgj9h98nal555415faa43vsydg161bdz
+
+Run the container:
+
+```console
+docker run -it hello-2.12-env:pgj9h98nal555415faa43vsydg161bdz
+```
+
+    [nix-shell:/build]$
+
+In the running container, run the build:
+
+```console
+buildDerivation
+```
+
+    unpacking sources
+    unpacking source archive /nix/store/8nqv6kshb3vs5q5bs2k600xpj5bkavkc-hello-2.12.tar.gz
+    ...
+    patching script interpreter paths in /nix/store/z5wwy5nagzy15gag42vv61c2agdpz2f2-hello-2.12
+    checking for references to /build/ in /nix/store/z5wwy5nagzy15gag42vv61c2agdpz2f2-hello-2.12...
+
+Check the build result:
+
+```console
+$out/bin/hello
+```
+
+    Hello, world!
diff --git a/pkgs/build-support/docker/default.nix b/pkgs/build-support/docker/default.nix
index e5b39cdd9dc..81e18c67959 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
@@ -1027,4 +1029,185 @@ 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 = ''
+          # Allows any user to create new directories in the Nix store (for the build result)
+          mkdir -p .${storeDir}
+          chmod a+w+t .${storeDir}
+
+          # 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";
 }