about summary refs log tree commit diff
path: root/nix
diff options
context:
space:
mode:
authorAspen Smith <root@gws.fyi>2024-02-18T18·57-0500
committerclbot <clbot@tvl.fyi>2024-02-21T20·52+0000
commit944483ef5e3536c96bf9120ef2054aa520cf6e60 (patch)
tree188a8341ae62d2b9431ea081ab172a559254edc9 /nix
parentd74c68025b5d1ffea3cd4f9731ee53b6ca2c6271 (diff)
feat(nix/writeTree): init r/7589
Add //nix/writeTree, a function to make a derivation to build a
directory structure from a Nix attribute set.

Co-authored-by: sterni <sternenseemann@systemli.org>
Change-Id: I9c0fc91611a55a20ad33de6f2b27abde4b6abd21
Reviewed-on: https://cl.tvl.fyi/c/depot/+/10963
Reviewed-by: sterni <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
Autosubmit: aspen <root@gws.fyi>
Reviewed-by: aspen <root@gws.fyi>
Diffstat (limited to 'nix')
-rw-r--r--nix/writeTree/OWNERS1
-rw-r--r--nix/writeTree/default.nix51
-rw-r--r--nix/writeTree/tests/default.nix93
3 files changed, 145 insertions, 0 deletions
diff --git a/nix/writeTree/OWNERS b/nix/writeTree/OWNERS
new file mode 100644
index 000000000000..b381c4e6604c
--- /dev/null
+++ b/nix/writeTree/OWNERS
@@ -0,0 +1 @@
+aspen
diff --git a/nix/writeTree/default.nix b/nix/writeTree/default.nix
new file mode 100644
index 000000000000..43ece9b19f97
--- /dev/null
+++ b/nix/writeTree/default.nix
@@ -0,0 +1,51 @@
+{ depot, lib, pkgs, ... }:
+let
+  inherit (lib) fix pipe mapAttrsToList isAttrs concatLines isString;
+
+  inherit (depot.nix.utils) isDirectory isRegularFile;
+
+  esc = s: lib.escapeShellArg /* ensure paths import into store */ "${s}";
+
+  writeTreeAtPath = path: tree:
+    ''
+      mkdir -p "$out/"${esc path}
+    ''
+    + pipe tree [
+      (mapAttrsToList (k: v:
+        # TODO(sterni): a more discoverable isPathLike would fit into //nix/utils
+        # ATTN: This check has the flaw that it accepts paths without context
+        # that would not be available in the sandbox!
+        if lib.types.path.check v then
+          if isRegularFile v then
+            "cp --reflink=auto ${esc v} \"$out/\"${esc path}/${esc k}"
+          else if isDirectory v then ''
+            mkdir -p "$out/"${esc path}
+            cp -r --reflink=auto ${esc v} "$out/"${esc path}/${esc k}
+          ''
+          else
+            throw "invalid path type (expected file or directory)"
+        else if isAttrs v then
+          writeTreeAtPath "${path}/${k}" v
+        else if isString v then
+          "cp --reflink=auto ${esc v} \"$out/\"${esc path}/${esc k}"
+        else
+          throw "invalid type (expected file, directory, or attrs)"))
+      concatLines
+    ];
+
+  /* Create a directory tree specified by a Nix attribute set structure.
+
+     Each value in `tree` should either be a file, a directory, or another tree
+     attribute set. Those paths will be written to a directory tree
+     corresponding to the structure of the attribute set.
+
+     Type: string -> attrSet -> derivation
+  */
+  writeTree = name: tree:
+    pkgs.runCommandLocal name { } (writeTreeAtPath "" tree);
+in
+
+# __functor trick so readTree can add the tests attribute
+{
+  __functor = _: writeTree;
+}
diff --git a/nix/writeTree/tests/default.nix b/nix/writeTree/tests/default.nix
new file mode 100644
index 000000000000..c5858ee96eb8
--- /dev/null
+++ b/nix/writeTree/tests/default.nix
@@ -0,0 +1,93 @@
+{ depot, pkgs, lib, ... }:
+
+let
+  inherit (pkgs) runCommand writeText writeTextFile;
+  inherit (depot.nix) writeTree;
+
+  checkTree = name: tree: expected:
+    runCommand "writeTree-test-${name}"
+      {
+        nativeBuildInputs = [ pkgs.buildPackages.lr ];
+        passAsFile = [ "expected" ];
+        inherit expected;
+      } ''
+      actualPath="$NIX_BUILD_TOP/actual"
+      cd ${lib.escapeShellArg (writeTree name tree)}
+      lr . > "$actualPath"
+      diff -u "$expectedPath" "$actualPath" | tee "$out"
+    '';
+in
+
+depot.nix.readTree.drvTargets {
+  empty = checkTree "empty" { }
+    ''
+      .
+    '';
+
+  simple-paths = checkTree "simple"
+    {
+      writeTree = {
+        meta = {
+          "owners.txt" = ../OWNERS;
+        };
+        "code.nix" = ../default.nix;
+        all-tests = ./.;
+        nested.dirs.eval-time = builtins.toFile "owothia" ''
+          hold me owo
+        '';
+      };
+    }
+    ''
+      .
+      ./writeTree
+      ./writeTree/all-tests
+      ./writeTree/all-tests/default.nix
+      ./writeTree/code.nix
+      ./writeTree/meta
+      ./writeTree/meta/owners.txt
+      ./writeTree/nested
+      ./writeTree/nested/dirs
+      ./writeTree/nested/dirs/eval-time
+    '';
+
+  empty-dirs = checkTree "empty-dirs"
+    {
+      this.dir.is.empty = { };
+      so.is.this.one = { };
+    }
+    ''
+      .
+      ./so
+      ./so/is
+      ./so/is/this
+      ./so/is/this/one
+      ./this
+      ./this/dir
+      ./this/dir/is
+      ./this/dir/is/empty
+    '';
+
+  drvs = checkTree "drvs"
+    {
+      file-drv = writeText "road.txt" ''
+        Any road followed precisely to its end leads precisely nowhere.
+      '';
+      dir-drv = writeTextFile {
+        name = "dir-of-text";
+        destination = "/text/in/more/dirs.txt";
+        text = ''
+          Climb the mountain just a little bit to test that it’s a mountain.
+          From the top of the mountain, you cannot see the mountain.
+        '';
+      };
+    }
+    ''
+      .
+      ./dir-drv
+      ./dir-drv/text
+      ./dir-drv/text/in
+      ./dir-drv/text/in/more
+      ./dir-drv/text/in/more/dirs.txt
+      ./file-drv
+    '';
+}