Skip to content

Commit

Permalink
writableDirWrapper: init
Browse files Browse the repository at this point in the history
  • Loading branch information
pluiedev committed Dec 4, 2024
1 parent d13c9db commit e41ae32
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 0 deletions.
116 changes: 116 additions & 0 deletions pkgs/by-name/wr/writableDirWrapper/writable-dir-wrapper/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
lib,
writeShellApplication,
xorg,
makeSetupHook,

makeWrapper,
}:
makeSetupHook {
name = "writable-dir-wrapper";

propagatedBuildInputs = [
makeWrapper
];

substitutions = {
lndir = lib.getExe xorg.lndir;
};

} ./wrapper.sh

/**
A wrapper for programs that require a writable directory to function, typically
games or proprietary software that expect read-only resources to be placed alongside
config files and state files (e.g. logs).
# Inputs
*`options`* (Attribute set)
: Set of options for the wrapper.
`name` (String)
: File name of the wrapper.
`version` (String, _optional_)
: Version of the underlying program. When set, version checks will be enabled and existing links will be recreated if they belong to a different version.
`path` (String)
: Path of the directory that would hold the linked data.
`links` (List of attrsets, _optional_)
: List of links from a source directory to a directory relative to the output path.
`src` (String)
: Source directory of the link, usually from the Nix store.
`dst` (String, _optional_)
: Destination directory of the link.
: _Default:_ the output path.
`postLink` (String, _optional_)
: Command to run after (re-)linking. Since links are first created in a temporary directory, `postLink` can be used to remove or add files that will be copied to the output path (for example, when some files need to be excluded from the linked result).
`exec` (String)
: Command to run.
`flags` (List of strings, _optional_)
: The list of flags given to the command. ***Unescaped and can refer to Bash variables and perform shell substitutions.***
# Type
```
writableDirWrapper ::
{
name :: String;
version? :: String;
path :: String;
links? :: [{ src :: String; dst? :: String }];
postLink? :: String;
exec :: String
} -> Derivation
```
# Examples
:::{.example}
## `pkgs.writableDirWrapper` usage example
```nix
{
writableDirWrapper,
hello,
}:
writableDirWrapper {
name = "writable-hello";
links = [
{
src = hello;
dst = "hello";
}
];
postLink = ''
echo "Hello world!" | base64 > hello.txt
'';
exec = ''
./hello/bin/hello --greeting=$(<"hello.txt")
'';
}
```
When built and run, this should output:
```console
SGVsbG8gd29ybGQhCg==
```
:::
*/
129 changes: 129 additions & 0 deletions pkgs/by-name/wr/writableDirWrapper/writable-dir-wrapper/wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# makeWritableDirWrapper EXECUTABLE OUT_PATH DIR ARGS

# ARGS:
# --dont-track-version : don't relink when version mismatches
#
# --post-link-run COMMAND : run command after linking
#
# --link SRC DST : link SRC to DST

makeWritableDirWrapper() {
local original="$1"; shift
local wrapper="$1"; shift
local dir="$1"; shift
local postLinkRun dontTrackVersion
local -A links

local makeWrapperParams=("$original" "$wrapper")
while [[ "$#" -gt 0 ]]; do
case "$1" in
--post-link-run)
postLinkRun="$2"; shift
;;
--dont-track-version)
dontTrackVersion=true
;;
--link)
src="$2"; shift
dst="$2"; shift
links["$src"]="$dst"
;;
*) makeWrapperParams+=("$1") ;;
esac
shift
done

local fragment=$(mktemp)

local unlinkCommands=()
local linkCommands=()
for src in "${!links[@]}"; do
dst=${links[$src]}
unlinkCommands+=("[[ -d \"$dir/$dst\" ]] && find \"$dir/$dst\" -type l -lname '$src' -delete;")
linkCommands+=(
"mkdir -p './$dst';"
"@lndir@ -silent '$src' './$dst';"
)
done


{
cat <<EOF
unlink() {
echo 'Removing existing file links'
${unlinkCommands[@]}
}
link() {
echo 'Linking files'
mkdir -p "$dir"
EOF

if [[ -z "$dontTrackVersion" ]]; then
echo " echo '$version' > \"$dir/.$pname.version\""
fi

# In case that postLink needs to remove some links to avoid conflict,
# we first link everything into a temporary directory then move over
cat <<EOF
(
cd \$(mktemp -d)
${linkCommands[@]}
$postLinkRun
cp -a ./. -t "$dir"
)
}
relink() {
[[ -d "$dir" ]] && unlink
link
}
declare manuallyRelink
for arg in "\$@"; do
case "\$arg" in
--relink-files) manuallyRelink=true ;;
*) args+=("\$arg") ;; # Passthrough all other args
esac
done
set -- "\${args[@]}"
if [[ -n "\$manuallyRelink" ]]; then
echo "Manually relinking files"; relink
elif [[ ! -d "$dir" ]]; then
echo "Directory $dir not found. Linking files..."; relink
EOF

if [[ -z "$dontTrackVersion" ]]; then
cat <<EOF
elif [[ ! -e "$dir/.$name.version" ]]; then
echo "Version file not found. Relinking files just to be safe..."; relink
elif [[ '$version' != "\$(<"$dir/.$pname.version")" ]]; then
echo "Current version is not the same as the newest version ($version). Relinking files"; relink
EOF
fi

echo "fi"
} >> "$fragment"

makeWrapper "${makeWrapperParams[@]}"

# Inject after the shebang
sed -i "1r $fragment" "$wrapper"
}

# Syntax: wrapProgramInWritableDir <PROGRAM> <DIRECTORY> <MAKE-WRAPPER FLAGS...>
wrapProgramInWritableDir() {
local prog="$1"
local dir="$2"
local hidden

assertExecutable "$prog"

hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped
while [ -e "$hidden" ]; do
hidden="${hidden}_"
done
mv "$prog" "$hidden"
makeWritableDirWrapper "$hidden" "$prog" "$dir" --inherit-argv0 "${@:3}"
}

0 comments on commit e41ae32

Please sign in to comment.