2021-09-09 00:04:55 +02:00
|
|
|
# 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
|
2021-10-09 16:48:14 +02:00
|
|
|
#
|
|
|
|
# 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.
|
2021-09-09 00:04:55 +02:00
|
|
|
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
|
2021-10-09 16:48:14 +02:00
|
|
|
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}";
|
2021-09-09 00:04:55 +02:00
|
|
|
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.
|
2022-09-26 19:33:05 +02:00
|
|
|
pkgs.runCommand "sparse-${builtins.baseNameOf root}" { } (
|
2021-09-09 00:04:55 +02:00
|
|
|
lib.concatMapStrings
|
|
|
|
({ src, dst }: ''
|
|
|
|
mkdir -p "$(dirname "$out${dst}")"
|
|
|
|
cp -aT --reflink=auto "${src}" "$out${dst}"
|
|
|
|
'')
|
|
|
|
symlinks
|
|
|
|
)
|