From 944483ef5e3536c96bf9120ef2054aa520cf6e60 Mon Sep 17 00:00:00 2001 From: Aspen Smith Date: Sun, 18 Feb 2024 13:57:12 -0500 Subject: feat(nix/writeTree): init Add //nix/writeTree, a function to make a derivation to build a directory structure from a Nix attribute set. Co-authored-by: sterni Change-Id: I9c0fc91611a55a20ad33de6f2b27abde4b6abd21 Reviewed-on: https://cl.tvl.fyi/c/depot/+/10963 Reviewed-by: sterni Tested-by: BuildkiteCI Autosubmit: aspen Reviewed-by: aspen --- nix/writeTree/OWNERS | 1 + nix/writeTree/default.nix | 51 ++++++++++++++++++++++ nix/writeTree/tests/default.nix | 93 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 nix/writeTree/OWNERS create mode 100644 nix/writeTree/default.nix create mode 100644 nix/writeTree/tests/default.nix (limited to 'nix/writeTree') 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 + ''; +} -- cgit 1.4.1