diff --git a/pkgs/build-support/setup-hooks/writable-dir-wrapper/default.nix b/pkgs/build-support/setup-hooks/writable-dir-wrapper/default.nix new file mode 100644 index 0000000000000..73fab8edd30cc --- /dev/null +++ b/pkgs/build-support/setup-hooks/writable-dir-wrapper/default.nix @@ -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== + ``` + ::: +*/ diff --git a/pkgs/build-support/setup-hooks/writable-dir-wrapper/wrapper.sh b/pkgs/build-support/setup-hooks/writable-dir-wrapper/wrapper.sh new file mode 100644 index 0000000000000..57147a0a2210a --- /dev/null +++ b/pkgs/build-support/setup-hooks/writable-dir-wrapper/wrapper.sh @@ -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 < \"$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 <> "$fragment" + + makeWrapper "${makeWrapperParams[@]}" + + # Inject after the shebang + sed -i "1r $fragment" "$wrapper" +} + +# Syntax: wrapProgramInWritableDir +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}" +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index c54b3abe186d9..83c3ed4d95ab9 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -789,6 +789,8 @@ with pkgs; releaseTools = callPackage ../build-support/release { }; + writableDirWrapper = callPackage ../build-support/setup-hooks/writable-dir-wrapper { }; + inherit (lib.systems) platforms; setJavaClassPath = makeSetupHook {