From 2aeaf65e8f9219c1acdb47bcf278983b3170a344 Mon Sep 17 00:00:00 2001 From: Robert Helgesson Date: Sun, 27 Jun 2021 16:42:45 +0200 Subject: [PATCH] files: assert that target files are unique Fixes #1807 --- modules/files.nix | 31 +++++++++++++++++++++++++ tests/modules/files/default.nix | 1 + tests/modules/files/target-conflict.nix | 26 +++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 tests/modules/files/target-conflict.nix diff --git a/modules/files.nix b/modules/files.nix index c0698ed75..9d67976d4 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -39,6 +39,28 @@ in }; config = { + assertions = [( + let + dups = + attrNames + (filterAttrs (n: v: v > 1) + (foldAttrs (acc: v: acc + v) 0 + (mapAttrsToList (n: v: { ${v.target} = 1; }) cfg))); + dupsStr = concatStringsSep ", " dups; + in { + assertion = dups == []; + message = '' + Conflicting managed target files: ${dupsStr} + + This may happen, for example, if you have a configuration similar to + + home.file = { + conflict1 = { source = ./foo.nix; target = "baz"; }; + conflict2 = { source = ./bar.nix; target = "baz"; }; + }''; + }) + ]; + lib.file.mkOutOfStoreSymlink = path: let pathStr = toString path; @@ -283,6 +305,15 @@ in local executable="$3" local recursive="$4" + # If the target already exists then we have a collision. Note, this + # should not happen due to the assertion found in the 'files' module. + # We therefore simply log the conflict and otherwise ignore it, mainly + # to make the `files-target-config` test work as expected. + if [[ -e "$realOut/$relTarget" ]]; then + echo "File conflict for file '$relTarget'" >&2 + return + fi + # Figure out the real absolute path to the target. local target target="$(realpath -m "$realOut/$relTarget")" diff --git a/tests/modules/files/default.nix b/tests/modules/files/default.nix index 6f1ef24b8..52e44d22b 100644 --- a/tests/modules/files/default.nix +++ b/tests/modules/files/default.nix @@ -3,6 +3,7 @@ files-hidden-source = ./hidden-source.nix; files-out-of-store-symlink = ./out-of-store-symlink.nix; files-source-with-spaces = ./source-with-spaces.nix; + files-target-conflict = ./target-conflict.nix; files-target-with-shellvar = ./target-with-shellvar.nix; files-text = ./text.nix; } diff --git a/tests/modules/files/target-conflict.nix b/tests/modules/files/target-conflict.nix new file mode 100644 index 000000000..dbcee80a0 --- /dev/null +++ b/tests/modules/files/target-conflict.nix @@ -0,0 +1,26 @@ +{ ... }: + +{ + config = { + home.file = { + conflict1 = { + text = ""; + target = "baz"; + }; + conflict2 = { + source = ./target-conflict.nix; + target = "baz"; + }; + }; + + test.asserts.assertions.expected = ['' + Conflicting managed target files: baz + + This may happen, for example, if you have a configuration similar to + + home.file = { + conflict1 = { source = ./foo.nix; target = "baz"; }; + conflict2 = { source = ./bar.nix; target = "baz"; }; + }'']; + }; +}