diff --git a/pkgs/build-support/writable-dir-wrapper/default.nix b/pkgs/build-support/writable-dir-wrapper/default.nix new file mode 100644 index 0000000000000..c97c6487890bb --- /dev/null +++ b/pkgs/build-support/writable-dir-wrapper/default.nix @@ -0,0 +1,186 @@ +{ + lib, + writeShellApplication, + xorg, +}: +/** + 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. + + `runtimeInputs` (List of strings or derivations, _optional_) + + : Paths containing executables that should be added to the script's `$PATH` variable, making them usable inside the script at runtime. + + `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. + + # Type + + ``` + writableDirWrapper :: + { + name :: String; + version? :: String; + path :: String; + links? :: [{ src :: String; dst? :: String }]; + runtimeInputs? :: [String|Derivation]; + 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== + ``` + ::: +*/ +{ + name, + version ? null, + path, + links ? [ ], + runtimeInputs ? [ ], + postLink ? "", + exec, +}@args: +let + args' = builtins.removeAttrs args [ + "version" + "path" + "runtimeInputs" + "links" + "postLink" + "exec" + ]; + + unlinkCommand = link: ''find "${path}/${link.dst or ""}" -type l -lname "${link.src}" -delete''; + linkCommand = link: '' + mkdir -p ./${link.dst or ""}; + lndir -silent ${link.src} "./${link.dst or ""}" + ''; +in +writeShellApplication ( + args' + // { + runtimeInputs = runtimeInputs ++ [ xorg.lndir ]; + + text = + '' + relink=false + + for arg in "$@"; do + case "$arg" in + --relink-files) relink=true ;; + # Passthrough all other args + *) args+=("$arg") ;; + esac + done + + versionFile="${path}/.${name}.version" + + if [[ "$relink" = "true" ]]; then + echo "Manually relinking files" + elif [[ ! -d "${path}" ]]; then + echo "Directory ${path} not found. Linking files..." + relink=true + '' + + lib.optionalString (version != null) '' + elif [[ ! -e "$versionFile" ]]; then + echo "Version file not found. Relinking files just to be safe..." + relink=true + elif [[ "${version}" != "$(<"$versionFile")" ]]; then + echo "Current version is not the same as the newest version (${version}). Relinking files" + relink=true + '' + + '' + fi + + if [[ "$relink" = "true" ]]; then + mkdir -p "${path}" + if [[ -d "${path}" ]]; then + echo "Removing existing file links" + ${lib.concatMapStringsSep "\n" unlinkCommand links} + fi + + echo "Linking files" + ${lib.optionalString (version != null) ''echo "${version}" > "$versionFile"''} + + # In case that postLink needs to remove some links to avoid conflict, + # we first link everything into a temporary directory then move over + ( + cd "$(mktemp -d)" + ${lib.concatMapStringsSep "\n" linkCommand links} + ${postLink} + cp -r ./* -t "${path}" + ) + fi + + ${exec} "''${args[@]}" + ''; + } +) diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index c54b3abe186d9..c5260b705640d 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/writable-dir-wrapper { }; + inherit (lib.systems) platforms; setJavaClassPath = makeSetupHook {