Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 84 additions & 20 deletions modules/top-level/files/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -76,28 +76,92 @@ in
);

# A directory with all the files in it
# Implementation based on NixOS's /etc module
build.extraFiles = pkgs.runCommandLocal "nvim-config" { passthru.vimPlugin = true; } ''
set -euo pipefail
# Implementation inspired by NixOS's /etc module
build.extraFiles =
pkgs.runCommandLocal "nvim-config"
{
__structuredAttrs = true;
targets = lib.catAttrs "target" extraFiles;
sources = lib.catAttrs "finalSource" extraFiles;
passthru.vimPlugin = true;
}
''
set -euo pipefail

numTargets=''${#targets[@]}

# Check if $1 is a prefix of $2
hasPrefix() {
case "$2" in
"$1"/*) return 0 ;;
*) return 1 ;;
esac
}

checkPrefix() {
if hasPrefix "$1" "$2"; then
echo "error: '$1' is a target, but another target conflicts: '$2'" >&2
exit 1
fi
}

# Fail if two targets conflict (duplicate or file/directory conflict)
checkConflict() {
local tgtA="$1"
local srcA="$2"
local tgtB="$3"
local srcB="$4"

# Exact duplicate with different content
if
[[ "$tgtA" == "$tgtB" ]] && [[ "$srcA" != "$srcB" ]] &&
! diff -q "$srcA" "$srcB" >/dev/null
then
echo "error: target '$tgtA' defined twice with different sources:" >&2
echo " $srcA" >&2
echo " $srcB" >&2
exit 1
fi

# Directory prefix conflicts
checkPrefix "$tgtA" "$tgtB"
checkPrefix "$tgtB" "$tgtA"
}

# Install symlink to source
installTarget() {
local tgt="$1"
local src="$2"
local dest="$out/$tgt"
mkdir -p "$(dirname "$dest")"
if [[ -e "$dest" ]]; then
local existing=$(readlink "$dest" || echo "$dest")
if [[ "$existing" != "$src" ]] && ! diff -q "$existing" "$src" >/dev/null; then
echo "error: duplicate target '$tgt' with conflicting content:" >&2
echo " $existing" >&2
echo " $src" >&2
exit 1
fi
return
fi
ln -s "$src" "$dest"
}

makeEntry() {
src="$1"
target="$2"
mkdir -p "$out/$(dirname "$target")"
cp "$src" "$out/$target"
}
# Validate all targets
for ((i=0; i<numTargets; i++)); do
for ((j=i+1; j<numTargets; j++)); do
checkConflict \
"''${targets[i]}" "''${sources[i]}" \
"''${targets[j]}" "''${sources[j]}"
done
done

mkdir -p "$out"
${lib.concatMapStringsSep "\n" (
{ target, finalSource, ... }:
lib.escapeShellArgs [
"makeEntry"
# Force local source paths to be added to the store
"${finalSource}"
target
]
) extraFiles}
'';
# Install all targets
mkdir -p "$out"
for ((i=0; i<numTargets; i++)); do
installTarget "''${targets[i]}" "''${sources[i]}"
done
'';

# Never combine user files with the rest of the plugins
performance.combinePlugins.standalonePlugins = [ config.build.extraFiles ];
Expand Down
191 changes: 180 additions & 11 deletions tests/test-sources/modules/extra-files.nix
Original file line number Diff line number Diff line change
@@ -1,15 +1,184 @@
let
checkExtraFiles =
pkgs:
{
name,
extraFilesDir,
expectedTargets,
}:
pkgs.runCommand name
{
inherit extraFilesDir expectedTargets;
__structuredAttrs = true;
}
''
set -euo pipefail

for target in "''${!expectedTargets[@]}"; do
file="$extraFilesDir/$target"
expected="''${expectedTargets["$target"]}"

if [ ! -e "$file" ]; then
echo "Missing target: $file"
exit 1
fi

if [ -n "$expected" ] && ! grep -qF "$expected" "$file"; then
echo "Content mismatch: $file"
exit 1
fi
done

touch $out
'';
in
{
# Example
query = {
extraFiles = {
"testing/test-case.nix".source = ./extra-files.nix;
"testing/123.txt".text = ''
One
Two
Three
'';
"queries/lua/injections.scm".text = ''
;; extends
'';
};
extraFiles."queries/lua/injections.scm".text = ''
;; extends
'';
};

happy-path =
{ config, pkgs, ... }:
{
extraFiles = {
# Basic tests
"testing/test-case.nix".source = ./extra-files.nix;
"testing/123.txt".text = ''
One
Two
Three
'';

# Multiple files in the same directory
"foo/a".text = "A";
"foo/b".text = "B";
"foo/c".text = "C";

# Nested deep path
"a/b/c/d/e.txt".text = "deep";

# Directory source
"beta/dir".source = pkgs.emptyDirectory;

# Mixed: source file + hello source
"hello/source".source = pkgs.hello;
};
test.runNvim = false;
test.extraInputs = [
(checkExtraFiles pkgs {
name = "check-happy-path";
extraFilesDir = config.build.extraFiles;
expectedTargets = {
"testing/test-case.nix" = null;
"testing/123.txt" = "One\nTwo\nThree";
"foo/a" = "A";
"foo/b" = "B";
"foo/c" = "C";
"a/b/c/d/e.txt" = "deep";
"beta/dir" = null;
"hello/source" = null;
};
})
];
};

# Special content edge cases
special-files =
{ config, pkgs, ... }:
{
extraFiles = {
"empty.txt".text = "";
"whitespace.txt".text = " \n\t ";
"файл.txt".text = "unicode";
"目录/文件.txt".text = "chinese";
};
test.runNvim = false;
test.extraInputs = [
(checkExtraFiles pkgs {
name = "check-special-files";
extraFilesDir = config.build.extraFiles;
expectedTargets = {
"empty.txt" = "";
"whitespace.txt" = " ";
"файл.txt" = "unicode";
"目录/文件.txt" = "chinese";
};
})
];
};

duplicate-mismatch =
{ config, pkgs, ... }:
{
extraFiles.a = {
target = "conflict.txt";
text = "abc";
};
extraFiles.b = {
target = "conflict.txt";
text = "xyz";
};
test.buildNixvim = false;
test.extraInputs = [
(pkgs.runCommand "expect-duplicate-failure"
{ failure = pkgs.testers.testBuildFailure config.build.extraFiles; }
''
exit_code=$(cat "$failure/testBuildFailure.exit")
(( exit_code == 1 )) || {
echo "Expected exit code 1"
exit 1
}
grep -q "target 'conflict.txt' defined twice with different sources" "$failure/testBuildFailure.log"
touch $out
''
)
];
};

file-vs-dir-conflict =
{ config, pkgs, ... }:
{
extraFiles."conflict".text = "file";
extraFiles."conflict/file.txt".text = "nested";
test.buildNixvim = false;
test.extraInputs = [
(pkgs.runCommand "expect-conflict-failure"
{ failure = pkgs.testers.testBuildFailure config.build.extraFiles; }
''
exit_code=$(cat "$failure/testBuildFailure.exit")
(( exit_code == 1 )) || {
echo "Expected exit code 1"
exit 1
}
grep -q "is a target, but another target conflicts:" "$failure/testBuildFailure.log"
touch $out
''
)
];
};

dir-vs-file-conflict =
{ config, pkgs, ... }:
{
extraFiles."conflictDir".source = pkgs.emptyDirectory;
extraFiles."conflictDir/file.txt".text = "nested";
test.buildNixvim = false;
test.extraInputs = [
(pkgs.runCommand "expect-dir-conflict-failure"
{ failure = pkgs.testers.testBuildFailure config.build.extraFiles; }
''
exit_code=$(cat "$failure/testBuildFailure.exit")
(( exit_code == 1 )) || {
echo "Expected exit code 1"
exit 1
}
grep -q "is a target, but another target conflicts:" "$failure/testBuildFailure.log"
touch $out
''
)
];
};
}