about summary refs log tree commit diff
path: root/nix/sparseTree
diff options
context:
space:
mode:
Diffstat (limited to 'nix/sparseTree')
-rw-r--r--nix/sparseTree/OWNERS3
-rw-r--r--nix/sparseTree/default.nix70
2 files changed, 73 insertions, 0 deletions
diff --git a/nix/sparseTree/OWNERS b/nix/sparseTree/OWNERS
new file mode 100644
index 000000000000..fdf6d7204051
--- /dev/null
+++ b/nix/sparseTree/OWNERS
@@ -0,0 +1,3 @@
+inherited: true
+owners:
+  - sterni
\ No newline at end of file
diff --git a/nix/sparseTree/default.nix b/nix/sparseTree/default.nix
new file mode 100644
index 000000000000..5184f33d5c46
--- /dev/null
+++ b/nix/sparseTree/default.nix
@@ -0,0 +1,70 @@
+# Build a “sparse” version of a given directory, only including contained files
+# and directories if they are listed in a supplied list:
+#
+# # A very minimal depot
+# sparseTree ./depot [
+#   ./default.nix
+#   ./depot/nix/readTree/default.nix
+#   ./third_party/nixpkgs
+#   ./third_party/overlays
+# ]
+{ pkgs, lib, ... }:
+
+# root path to use as a reference point
+root:
+# list of paths below `root` that should be
+# included in the resulting directory
+#
+# If path, need to refer to the actual file / directory to be included.
+# If a string, it is treated as a string relative to the root.
+paths:
+
+let
+  rootLength = builtins.stringLength (toString root);
+
+  # Count slashes in a path.
+  #
+  # Type: path -> int
+  depth = path: lib.pipe path [
+    toString
+    (builtins.split "/")
+    (builtins.filter builtins.isList)
+    builtins.length
+  ];
+
+  # (Parent) directories will be created from deepest to shallowest
+  # which should mean no conflicts are caused unless both a child
+  # and its parent directory are in the list of paths.
+  # TODO(sterni): improve error messages in such cases
+  fromDeepest = lib.sort (a: b: depth a < depth b) paths;
+
+  # Create a set which contains the source path to copy / symlink and
+  # it's destination, so the path below the destination root including
+  # a leading slash. Additionally some sanity checking is done.
+  makeSymlink = path:
+    let
+      withLeading = p: if builtins.substring 0 1 p == "/" then p else "/" + p;
+      fullPath =
+        /**/ if builtins.isPath path then path
+        else if builtins.isString path then (root + withLeading path)
+        else builtins.throw "Unsupported path type ${builtins.typeOf path}";
+      strPath = toString fullPath;
+      contextPath = "${fullPath}";
+      belowRoot = builtins.substring rootLength (-1) strPath;
+      prefix = builtins.substring 0 rootLength strPath;
+    in assert toString root == prefix; {
+      src = contextPath;
+      dst = belowRoot;
+    };
+
+  symlinks = builtins.map makeSymlink fromDeepest;
+in
+
+# TODO(sterni): teach readTree to also read symlinked directories,
+# so we ln -sT instead of cp -aT.
+pkgs.runCommandNoCC "sparse-${builtins.baseNameOf root}" {} (
+  lib.concatMapStrings ({ src, dst }: ''
+    mkdir -p "$(dirname "$out${dst}")"
+    cp -aT --reflink=auto "${src}" "$out${dst}"
+  '') symlinks
+)