Skip to content

Commit

Permalink
Merge pull request #12 from loqusion/fix/copyVirtualPathsHook
Browse files Browse the repository at this point in the history
fix(#13): handle `virtualPaths` edge cases
  • Loading branch information
loqusion authored May 2, 2024
2 parents 6e290fd + a58fe63 commit 904906d
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 38 deletions.
2 changes: 2 additions & 0 deletions checks/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ in
src = myLib.cleanTypstSource ./simple-with-virtual-paths;
};

virtualPathsChecks = callPackage ./virtual-paths.nix {};

watch = callPackage ./watch.nix {};
watchSimple = watch {} {
inherit typstSource;
Expand Down
1 change: 1 addition & 0 deletions checks/fixtures/more-icons/another-link.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
181 changes: 181 additions & 0 deletions checks/virtual-paths.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
{
cleanTypstSource,
copyVirtualPathsHook,
lib,
linkFarmFromDrvs,
linkVirtualPaths,
pkgs,
}: let
inherit (lib.attrsets) filterAttrs mapAttrsToList;
inherit (lib.lists) all;
inherit (lib.strings) toShellVars;

cleanArgs = args:
builtins.removeAttrs args [
"virtualPaths"
"assertionCommand"
"linkAssertionCommnad"
];

copyVirtualPathsHookAssertion = args @ {
virtualPaths,
assertionCommand,
...
}:
pkgs.stdenv.mkDerivation ((cleanArgs args)
// {
src = cleanTypstSource ./simple;
nativeBuildInputs = [
(copyVirtualPathsHook virtualPaths)
];
buildPhase = ''
runHook preBuild
runHook postBuild
'';
postBuild = ''
set -euo pipefail
assertionCommand() {
${assertionCommand}
}
if ! assertionCommand; then
${toShellVars {assertionText = assertionCommand;}}
echo "assertion \`$assertionText\` failed"
exit 1
fi
touch "$out"
'';
});

linkVirtualPathsAssertion = args @ {
virtualPaths,
assertionCommand,
linkAssertionCommand ? "true",
...
}:
pkgs.stdenv.mkDerivation ((cleanArgs args)
// {
src = cleanTypstSource ./simple;
buildPhase = ''
${linkVirtualPaths {inherit virtualPaths;}}
runHook postBuild
'';
postBuild = ''
set -euo pipefail
assertionCommand() {
${assertionCommand}
}
linkAssertionCommand() {
${linkAssertionCommand}
}
if ! assertionCommand; then
${toShellVars {assertionText = assertionCommand;}}
echo "assertion \`$assertionText\` failed"
exit 1
fi
if ! linkAssertionCommand; then
${toShellVars {assertionText = linkAssertionCommand;}}
echo "assertion \`$assertionText\` failed"
exit 1
fi
touch "$out"
'';
});

virtualPathsTestAttrs = {
fileSource = {
virtualPaths = ["${./fixtures/icons}/link.svg"];
assertionCommand = ''[ -f ./link.svg ]'';
linkAssertionCommand = ''[ -L ./link.svg ]'';
};

directorySource = {
virtualPaths = [./fixtures/icons];
assertionCommand = ''[ -f ./main.typ ] && [ -f ./link.svg ]'';
linkAssertionCommand = ''[ -L ./link.svg ]'';
};

fileSourceWithDest = {
virtualPaths = [
{
src = ./fixtures/icons/link.svg;
dest = "link.svg";
}
];
assertionCommand = ''[ -f ./link.svg ]'';
linkAssertionCommand = ''[ -L ./link.svg ]'';
};

directorySourceWithDest = {
virtualPaths = [
{
src = ./fixtures/icons;
dest = "icons";
}
];
assertionCommand = ''[ -d ./icons ] && [ -f ./icons/link.svg ]'';
linkAssertionCommand = ''[ ! -L ./icons ] && [ -L ./icons/link.svg ]'';
};

fileSourceWithDeepDest = {
virtualPaths = [
{
src = ./fixtures/icons/link.svg;
dest = "assets/icons/link.svg";
}
];
assertionCommand = ''[ -d ./assets/icons ] && [ -f ./assets/icons/link.svg ]'';
linkAssertionCommand = ''[ ! -L ./assets ] && [ ! -L ./assets/icons ] && [ -L ./assets/icons/link.svg ]'';
};

directorySourceWithDeepDest = {
virtualPaths = [
{
src = ./fixtures/icons;
dest = "assets/icons";
}
];
assertionCommand = ''[ -d ./assets/icons ] && [ -f ./assets/icons/link.svg ]'';
linkAssertionCommand = ''[ ! -L ./assets ] && [ ! -L ./assets/icons ] && [ -L ./assets/icons/link.svg ]'';
};

mergedSources = {
virtualPaths = [
{
src = ./fixtures/icons;
dest = "icons";
}
{
src = ./fixtures/more-icons;
dest = "icons";
}
];
assertionCommand = ''[ -d ./icons ] && [ -f ./icons/link.svg ] && [ -f ./icons/another-link.svg ]'';
linkAssertionCommand = ''[ ! -L ./icons ] && [ -L ./icons/link.svg ] && [ -L ./icons/another-link.svg ]'';
};
};

skip = {
copyVirtualPathsHook = [];
linkVirtualPaths = [];
};
filterSkip = skipNames:
filterAttrs
(name: _: all (skipName: name != skipName) skipNames);

mapTestAttrs = f: skipAttrs: testGroupName:
mapAttrsToList
(name: attrs: f (attrs // {name = "${testGroupName}-${name}";}))
(filterSkip skipAttrs virtualPathsTestAttrs);
in
linkFarmFromDrvs "virtualPathsTests"
(
(mapTestAttrs copyVirtualPathsHookAssertion skip.copyVirtualPathsHook "copyVirtualPathsHook")
++ (mapTestAttrs linkVirtualPathsAssertion skip.linkVirtualPaths "linkVirtualPaths")
)
7 changes: 5 additions & 2 deletions docs/api/derivations/common/virtual-paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ Useful for projects which rely on remote resources, such as

Each element of the list is an attribute set with the following keys:

- `src`: path to source directory
- `dest` (optional): path where files will be made available (defaults to `.`)
- `src`: path to source file or directory
- `dest` (optional): path where file(s) will be made available (defaults to `.`)
- If `src` is a directory, `dest` will be a directory containing those files.
- Specifying the same `dest` for multiple `src` directories will merge them.
- If `src` is a file, `dest` will be a copy of that file.

Instead of an attrset, you may use a path which will be interpreted the same as
if you had specified an attrset with just `src`.
Expand Down
98 changes: 66 additions & 32 deletions lib/linkVirtualPaths.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,74 @@
inherit (pkgs) symlinkJoin;
inherit (lib) optionalString;
inherit (lib.filesystem) pathIsDirectory;
inherit (lib.strings) concatMapStringsSep escapeShellArg;
inherit (lib.strings) concatMapStringsSep toShellVars;

shellUtils = ''
_same_path() {
[ "$(realpath "$1")" = "$(realpath "$2")" ]
}
_ensure_parent_exists() {
mkdir -p "$(dirname "$1")"
}
_cleanup_parent() {
local parent
parent=$(dirname "$1")
if ! _same_path "." "$1" && ! _same_path "." "$parent"; then
rmdir -p --ignore-fail-on-non-empty "$parent"
fi
}
'';
in
{
forceVirtualPaths ? false,
virtualPaths,
}:
concatMapStringsSep
"\n" (virtualPath_: let
virtualPath = coerceVirtualPathAttr virtualPath_;
source =
if !pathIsDirectory (toString virtualPath.src)
then virtualPath.src
else
(symlinkJoin {
name = "symlink" + optionalString (virtualPath ? dest) "-${virtualPath.dest}";
paths = [virtualPath.src];
});
lnAdditionalOpts = optionalString forceVirtualPaths "--force";
cpAdditionalOpts =
if forceVirtualPaths
then "--force"
else "--no-clobber";
in
# We don't want a refusal to overwrite existing files to cause nix to fail, so we add `|| true`
# to the commands this applies to
''
if [ ! -d ${escapeShellArg source} ]; then
echo "typix: linking ${virtualPath.src} to ${virtualPath.dest}"
ln ${lnAdditionalOpts} -sT ${escapeShellArg source} ${escapeShellArg virtualPath.dest} ||
true
else
echo "typix: linking ${virtualPath.src} to ${virtualPath.dest} recursively"
cp ${cpAdditionalOpts} -RT --no-dereference --no-preserve=mode ${escapeShellArg source} ${escapeShellArg virtualPath.dest} ||
true
fi
'')
virtualPaths
shellUtils
+ "\n"
+ (concatMapStringsSep
"\n" (virtualPath_: let
virtualPath = coerceVirtualPathAttr virtualPath_;
source =
if !pathIsDirectory (toString virtualPath.src)
then virtualPath.src
else
(symlinkJoin {
name = "symlink" + optionalString (virtualPath ? dest) "-${virtualPath.dest}";
paths = [virtualPath.src];
});
lnAdditionalOpts = optionalString forceVirtualPaths "--force";
cpAdditionalOpts =
if forceVirtualPaths
then "--force"
else "--no-clobber";
in
# We don't want a refusal to overwrite existing files to cause nix to fail, so we add `|| true`
# to the commands this applies to
''
${toShellVars {
virtualPathSrc = source;
virtualPathDest = virtualPath.dest;
}}
_ensure_parent_exists "$virtualPathDest"
if [ -f "$virtualPathSrc" ]; then
echo "typix: linking ${virtualPath.src} to $virtualPathDest"
if _same_path "." "$virtualPathDest"; then
ln ${lnAdditionalOpts} -sv "$virtualPathSrc" "$virtualPathDest" ||
true
else
ln ${lnAdditionalOpts} -sTv "$virtualPathSrc" "$virtualPathDest" ||
true
fi
else
echo "typix: linking ${virtualPath.src} to $virtualPathDest recursively"
cp ${cpAdditionalOpts} -RTv --no-dereference --no-preserve=mode "$virtualPathSrc" "$virtualPathDest" ||
true
fi
_cleanup_parent "$virtualPathDest"
'')
virtualPaths)
14 changes: 10 additions & 4 deletions lib/setupHooks/copyVirtualPaths.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@
virtualPathSrc = virtualPath.src;
virtualPathDest = virtualPath.dest;
}}
mkdir -p "$virtualPathDest"
echo "Copying $virtualPathSrc to $virtualPathDest"
cp -LTR --reflink=auto --no-preserve=mode "$virtualPathSrc" "$virtualPathDest"
if [ "$virtualPathDest" != "." ]; then
rmdir --ignore-fail-on-non-empty "$virtualPathDest"
_ensure_parent_exists "$virtualPathDest"
if [ -f "$virtualPathSrc" ] && _same_path "." "$virtualPathDest"; then
cp -Lv --reflink=auto --no-preserve=mode "$virtualPathSrc" "$virtualPathDest"
else
cp -LTRv --reflink=auto --no-preserve=mode "$virtualPathSrc" "$virtualPathDest"
fi
_cleanup_parent "$virtualPathDest"
'')
virtualPaths;
in
Expand Down
16 changes: 16 additions & 0 deletions lib/setupHooks/copyVirtualPathsHook.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
_same_path() {
[ "$(realpath "$1")" = "$(realpath "$2")" ]
}

_ensure_parent_exists() {
mkdir -p "$(dirname "$1")"
}

_cleanup_parent() {
local parent
parent=$(dirname "$1")
if ! _same_path "." "$1" && ! _same_path "." "$parent"; then
rmdir -p --ignore-fail-on-non-empty "$parent"
fi
}

copyVirtualPaths() {
:
@copyAllVirtualPaths@
Expand Down

0 comments on commit 904906d

Please sign in to comment.