about summary refs log tree commit diff
path: root/nixpkgs/pkgs/build-support/setup-hooks
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/pkgs/build-support/setup-hooks')
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/audit-tmpdir.sh39
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/auto-patchelf.py378
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/auto-patchelf.sh108
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/autoreconf.sh7
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/breakpoint-hook.sh9
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/compress-man-pages.sh33
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/copy-desktop-items.sh43
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/copy-pkgconfig-items.sh46
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/desktop-to-darwin-bundle.sh245
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/die.sh21
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/enable-coverage-instrumentation.sh20
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/find-xml-catalogs.sh22
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/fix-darwin-dylib-names.sh40
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/gog-unpack.sh11
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/install-shell-files.sh230
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/keep-build-tree.sh6
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/ld-is-cc-hook.sh5
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/make-binary-wrapper/default.nix27
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/make-binary-wrapper/make-binary-wrapper.sh471
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/make-coverage-analysis-report.sh25
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/make-symlinks-relative.sh37
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh225
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/move-build-tree.sh12
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/move-docs.sh27
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/move-lib64.sh22
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/move-sbin.sh19
-rwxr-xr-xnixpkgs/pkgs/build-support/setup-hooks/move-systemd-user-units.sh25
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/mpi-check-hook/default.nix5
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/mpi-check-hook/mpi-check-hook.sh54
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/multiple-outputs.sh214
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/default.nix18
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh183
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-lines.awk50
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/test.nix40
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/test.ppd22
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/default.nix60
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-bash.sh50
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-csh.sh57
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-fish.sh50
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-posix.sh39
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/default.nix442
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.bash2
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.csh.in1
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.fish9
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.sh.in2
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-bash21
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-csh13
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-fish13
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-posix21
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/patch-shebangs.sh148
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/default.nix8
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/postgresql-test-hook.sh83
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/test.nix30
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/prune-libtool-files.sh22
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/reproducible-builds.sh11
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/role.bash71
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/separate-debug-info.sh52
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/set-java-classpath.sh13
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/set-source-date-epoch-to-latest.sh34
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/setup-debug-info-dirs.sh5
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/shorten-perl-shebang.sh88
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/strip-java-archives.sh16
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/strip.sh102
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh12
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/use-old-cxx-abi.sh1
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/validate-pkg-config.sh18
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/win-dll-link.sh89
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix203
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/tests/lib.nix31
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/tests/sample-project/Makefile30
-rw-r--r--nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh89
71 files changed, 4675 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/audit-tmpdir.sh b/nixpkgs/pkgs/build-support/setup-hooks/audit-tmpdir.sh
new file mode 100644
index 000000000000..36714178156b
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/audit-tmpdir.sh
@@ -0,0 +1,39 @@
+# Check whether RPATHs or wrapper scripts contain references to
+# $TMPDIR. This is a serious security bug because it allows any user
+# to inject files into search paths of other users' processes.
+#
+# It might be better to have Nix scan build output for any occurrence
+# of $TMPDIR (which would also be good for reproducibility), but at
+# the moment that would produce too many spurious errors (e.g. debug
+# info or assertion messages that refer to $TMPDIR).
+
+fixupOutputHooks+=('if [[ -z "${noAuditTmpdir-}" && -e "$prefix" ]]; then auditTmpdir "$prefix"; fi')
+
+auditTmpdir() {
+    local dir="$1"
+    [ -e "$dir" ] || return 0
+
+    echo "checking for references to $TMPDIR/ in $dir..."
+
+    local i
+    find "$dir" -type f -print0 | while IFS= read -r -d $'\0' i; do
+        if [[ "$i" =~ .build-id ]]; then continue; fi
+
+        if isELF "$i"; then
+            if { printf :; patchelf --print-rpath "$i"; } | grep -q -F ":$TMPDIR/"; then
+                echo "RPATH of binary $i contains a forbidden reference to $TMPDIR/"
+                exit 1
+            fi
+        fi
+
+        if isScript "$i"; then
+            if [ -e "$(dirname "$i")/.$(basename "$i")-wrapped" ]; then
+                if grep -q -F "$TMPDIR/" "$i"; then
+                    echo "wrapper script $i contains a forbidden reference to $TMPDIR/"
+                    exit 1
+                fi
+            fi
+        fi
+
+    done
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/auto-patchelf.py b/nixpkgs/pkgs/build-support/setup-hooks/auto-patchelf.py
new file mode 100644
index 000000000000..4769179167b3
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/auto-patchelf.py
@@ -0,0 +1,378 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import pprint
+import subprocess
+import sys
+from fnmatch import fnmatch
+from collections import defaultdict
+from contextlib import contextmanager
+from dataclasses import dataclass
+from itertools import chain
+from pathlib import Path, PurePath
+from typing import DefaultDict, Iterator, List, Optional, Set, Tuple
+
+from elftools.common.exceptions import ELFError  # type: ignore
+from elftools.elf.dynamic import DynamicSection  # type: ignore
+from elftools.elf.elffile import ELFFile  # type: ignore
+from elftools.elf.enums import ENUM_E_TYPE, ENUM_EI_OSABI  # type: ignore
+
+
+@contextmanager
+def open_elf(path: Path) -> Iterator[ELFFile]:
+    with path.open('rb') as stream:
+        yield ELFFile(stream)
+
+
+def is_static_executable(elf: ELFFile) -> bool:
+    # Statically linked executables have an ELF type of EXEC but no INTERP.
+    return (elf.header["e_type"] == 'ET_EXEC'
+            and not elf.get_section_by_name(".interp"))
+
+
+def is_dynamic_executable(elf: ELFFile) -> bool:
+    # We do not require an ELF type of EXEC. This also catches
+    # position-independent executables, as they typically have an INTERP
+    # section but their ELF type is DYN.
+    return bool(elf.get_section_by_name(".interp"))
+
+
+def get_dependencies(elf: ELFFile) -> List[str]:
+    dependencies = []
+    # This convoluted code is here on purpose. For some reason, using
+    # elf.get_section_by_name(".dynamic") does not always return an
+    # instance of DynamicSection, but that is required to call iter_tags
+    for section in elf.iter_sections():
+        if isinstance(section, DynamicSection):
+            for tag in section.iter_tags('DT_NEEDED'):
+                dependencies.append(tag.needed)
+            break # There is only one dynamic section
+
+    return dependencies
+
+
+def get_rpath(elf: ELFFile) -> List[str]:
+    # This convoluted code is here on purpose. For some reason, using
+    # elf.get_section_by_name(".dynamic") does not always return an
+    # instance of DynamicSection, but that is required to call iter_tags
+    for section in elf.iter_sections():
+        if isinstance(section, DynamicSection):
+            for tag in section.iter_tags('DT_RUNPATH'):
+                return tag.runpath.split(':')
+
+            for tag in section.iter_tags('DT_RPATH'):
+                return tag.rpath.split(':')
+
+            break # There is only one dynamic section
+
+    return []
+
+
+def get_arch(elf: ELFFile) -> str:
+    return elf.get_machine_arch()
+
+
+def get_osabi(elf: ELFFile) -> str:
+    return elf.header["e_ident"]["EI_OSABI"]
+
+
+def osabi_are_compatible(wanted: str, got: str) -> bool:
+    """
+    Tests whether two OS ABIs are compatible, taking into account the
+    generally accepted compatibility of SVR4 ABI with other ABIs.
+    """
+    if not wanted or not got:
+        # One of the types couldn't be detected, so as a fallback we'll
+        # assume they're compatible.
+        return True
+
+    # Generally speaking, the base ABI (0x00), which is represented by
+    # readelf(1) as "UNIX - System V", indicates broad compatibility
+    # with other ABIs.
+    #
+    # TODO: This isn't always true. For example, some OSes embed ABI
+    # compatibility into SHT_NOTE sections like .note.tag and
+    # .note.ABI-tag.  It would be prudent to add these to the detection
+    # logic to produce better ABI information.
+    if wanted == 'ELFOSABI_SYSV':
+        return True
+
+    # Similarly here, we should be able to link against a superset of
+    # features, so even if the target has another ABI, this should be
+    # fine.
+    if got == 'ELFOSABI_SYSV':
+        return True
+
+    # Otherwise, we simply return whether the ABIs are identical.
+    return wanted == got
+
+
+def glob(path: Path, pattern: str, recursive: bool) -> Iterator[Path]:
+    if path.is_dir():
+        return path.rglob(pattern) if recursive else path.glob(pattern)
+    else:
+        # path.glob won't return anything if the path is not a directory.
+        # We extend that behavior by matching the file name against the pattern.
+        # This allows to pass single files instead of dirs to auto_patchelf,
+        # for greater control on the files to consider.
+        return [path] if path.match(pattern) else []
+
+
+cached_paths: Set[Path] = set()
+soname_cache: DefaultDict[Tuple[str, str], List[Tuple[Path, str]]] = defaultdict(list)
+
+
+def populate_cache(initial: List[Path], recursive: bool =False) -> None:
+    lib_dirs = list(initial)
+
+    while lib_dirs:
+        lib_dir = lib_dirs.pop(0)
+
+        if lib_dir in cached_paths:
+            continue
+
+        cached_paths.add(lib_dir)
+
+        for path in glob(lib_dir, "*.so*", recursive):
+            if not path.is_file():
+                continue
+
+            # As an optimisation, resolve the symlinks here, as the target is unique
+            # XXX: (layus, 2022-07-25) is this really an optimisation in all cases ?
+            # It could make the rpath bigger or break the fragile precedence of $out.
+            resolved = path.resolve()
+            # Do not use resolved paths when names do not match
+            if resolved.name != path.name:
+                resolved = path
+
+            try:
+                with open_elf(path) as elf:
+                    osabi = get_osabi(elf)
+                    arch = get_arch(elf)
+                    rpath = [Path(p) for p in get_rpath(elf)
+                                     if p and '$ORIGIN' not in p]
+                    lib_dirs += rpath
+                    soname_cache[(path.name, arch)].append((resolved.parent, osabi))
+
+            except ELFError:
+                # Not an ELF file in the right format
+                pass
+
+
+def find_dependency(soname: str, soarch: str, soabi: str) -> Optional[Path]:
+    for lib, libabi in soname_cache[(soname, soarch)]:
+        if osabi_are_compatible(soabi, libabi):
+            return lib
+    return None
+
+
+@dataclass
+class Dependency:
+    file: Path              # The file that contains the dependency
+    name: Path              # The name of the dependency
+    found: bool = False     # Whether it was found somewhere
+
+
+def auto_patchelf_file(path: Path, runtime_deps: list[Path], append_rpaths: List[Path] = [], extra_args: List[str] = []) -> list[Dependency]:
+    try:
+        with open_elf(path) as elf:
+
+            if is_static_executable(elf):
+                # No point patching these
+                print(f"skipping {path} because it is statically linked")
+                return []
+
+            if elf.num_segments() == 0:
+                # no segment (e.g. object file)
+                print(f"skipping {path} because it contains no segment")
+                return []
+
+            file_arch = get_arch(elf)
+            if interpreter_arch != file_arch:
+                # Our target architecture is different than this file's
+                # architecture, so skip it.
+                print(f"skipping {path} because its architecture ({file_arch})"
+                      f" differs from target ({interpreter_arch})")
+                return []
+
+            file_osabi = get_osabi(elf)
+            if not osabi_are_compatible(interpreter_osabi, file_osabi):
+                print(f"skipping {path} because its OS ABI ({file_osabi}) is"
+                      f" not compatible with target ({interpreter_osabi})")
+                return []
+
+            file_is_dynamic_executable = is_dynamic_executable(elf)
+
+            file_dependencies = map(Path, get_dependencies(elf))
+
+    except ELFError:
+        return []
+
+    rpath = []
+    if file_is_dynamic_executable:
+        print("setting interpreter of", path)
+        subprocess.run(
+                ["patchelf", "--set-interpreter", interpreter_path.as_posix(), path.as_posix()] + extra_args,
+                check=True)
+        rpath += runtime_deps
+
+    print("searching for dependencies of", path)
+    dependencies = []
+    # Be sure to get the output of all missing dependencies instead of
+    # failing at the first one, because it's more useful when working
+    # on a new package where you don't yet know the dependencies.
+    for dep in file_dependencies:
+        if dep.is_absolute() and dep.is_file():
+            # This is an absolute path. If it exists, just use it.
+            # Otherwise, we probably want this to produce an error when
+            # checked (because just updating the rpath won't satisfy
+            # it).
+            continue
+        elif (libc_lib / dep).is_file():
+            # This library exists in libc, and will be correctly
+            # resolved by the linker.
+            continue
+
+        if found_dependency := find_dependency(dep.name, file_arch, file_osabi):
+            rpath.append(found_dependency)
+            dependencies.append(Dependency(path, dep, True))
+            print(f"    {dep} -> found: {found_dependency}")
+        else:
+            dependencies.append(Dependency(path, dep, False))
+            print(f"    {dep} -> not found!")
+
+    rpath.extend(append_rpaths)
+
+    # Dedup the rpath
+    rpath_str = ":".join(dict.fromkeys(map(Path.as_posix, rpath)))
+
+    if rpath:
+        print("setting RPATH to:", rpath_str)
+        subprocess.run(
+                ["patchelf", "--set-rpath", rpath_str, path.as_posix()] + extra_args,
+                check=True)
+
+    return dependencies
+
+
+def auto_patchelf(
+        paths_to_patch: List[Path],
+        lib_dirs: List[Path],
+        runtime_deps: List[Path],
+        recursive: bool = True,
+        ignore_missing: List[str] = [],
+        append_rpaths: List[Path] = [],
+        extra_args: List[str] = []) -> None:
+
+    if not paths_to_patch:
+        sys.exit("No paths to patch, stopping.")
+
+    # Add all shared objects of the current output path to the cache,
+    # before lib_dirs, so that they are chosen first in find_dependency.
+    populate_cache(paths_to_patch, recursive)
+    populate_cache(lib_dirs)
+
+    dependencies = []
+    for path in chain.from_iterable(glob(p, '*', recursive) for p in paths_to_patch):
+        if not path.is_symlink() and path.is_file():
+            dependencies += auto_patchelf_file(path, runtime_deps, append_rpaths, extra_args)
+
+    missing = [dep for dep in dependencies if not dep.found]
+
+    # Print a summary of the missing dependencies at the end
+    print(f"auto-patchelf: {len(missing)} dependencies could not be satisfied")
+    failure = False
+    for dep in missing:
+        for pattern in ignore_missing:
+            if fnmatch(dep.name.name, pattern):
+                print(f"warn: auto-patchelf ignoring missing {dep.name} wanted by {dep.file}")
+                break
+        else:
+            print(f"error: auto-patchelf could not satisfy dependency {dep.name} wanted by {dep.file}")
+            failure = True
+
+    if failure:
+        sys.exit('auto-patchelf failed to find all the required dependencies.\n'
+                 'Add the missing dependencies to --libs or use '
+                 '`--ignore-missing="foo.so.1 bar.so etc.so"`.')
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(
+        prog="auto-patchelf",
+        description='auto-patchelf tries as hard as possible to patch the'
+                    ' provided binary files by looking for compatible'
+                    'libraries in the provided paths.')
+    parser.add_argument(
+        "--ignore-missing",
+        nargs="*",
+        type=str,
+        help="Do not fail when some dependencies are not found.")
+    parser.add_argument(
+        "--no-recurse",
+        dest="recursive",
+        action="store_false",
+        help="Disable the recursive traversal of paths to patch.")
+    parser.add_argument(
+        "--paths", nargs="*", type=Path,
+        help="Paths whose content needs to be patched."
+             " Single files and directories are accepted."
+             " Directories are traversed recursively by default.")
+    parser.add_argument(
+        "--libs", nargs="*", type=Path,
+        help="Paths where libraries are searched for."
+             " Single files and directories are accepted."
+             " Directories are not searched recursively.")
+    parser.add_argument(
+        "--runtime-dependencies", nargs="*", type=Path,
+        help="Paths to prepend to the runtime path of executable binaries."
+             " Subject to deduplication, which may imply some reordering.")
+    parser.add_argument(
+        "--append-rpaths",
+        nargs="*",
+        type=Path,
+        help="Paths to append to all runtime paths unconditionally",
+    )
+    parser.add_argument(
+        "--extra-args",
+        # Undocumented Python argparse feature: consume all remaining arguments
+        # as values for this one. This means this argument should always be passed
+        # last.
+        nargs="...",
+        type=str,
+        help="Extra arguments to pass to patchelf. This argument should always come last."
+    )
+
+    print("automatically fixing dependencies for ELF files")
+    args = parser.parse_args()
+    pprint.pprint(vars(args))
+
+    auto_patchelf(
+        args.paths,
+        args.libs,
+        args.runtime_dependencies,
+        args.recursive,
+        args.ignore_missing,
+        append_rpaths=args.append_rpaths,
+        extra_args=args.extra_args)
+
+
+interpreter_path: Path  = None # type: ignore
+interpreter_osabi: str  = None # type: ignore
+interpreter_arch: str   = None # type: ignore
+libc_lib: Path          = None # type: ignore
+
+if __name__ == "__main__":
+    nix_support = Path(os.environ['NIX_BINTOOLS']) / 'nix-support'
+    interpreter_path = Path((nix_support / 'dynamic-linker').read_text().strip())
+    libc_lib = Path((nix_support / 'orig-libc').read_text().strip()) / 'lib'
+
+    with open_elf(interpreter_path) as interpreter:
+        interpreter_osabi = get_osabi(interpreter)
+        interpreter_arch = get_arch(interpreter)
+
+    if interpreter_arch and interpreter_osabi and interpreter_path and libc_lib:
+        main()
+    else:
+        sys.exit("Failed to parse dynamic linker (ld) properties.")
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/auto-patchelf.sh b/nixpkgs/pkgs/build-support/setup-hooks/auto-patchelf.sh
new file mode 100644
index 000000000000..783ea45f8eeb
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/auto-patchelf.sh
@@ -0,0 +1,108 @@
+# shellcheck shell=bash
+
+declare -a autoPatchelfLibs
+declare -a extraAutoPatchelfLibs
+
+gatherLibraries() {
+    autoPatchelfLibs+=("$1/lib")
+}
+
+# shellcheck disable=SC2154
+# (targetOffset is referenced but not assigned.)
+addEnvHooks "$targetOffset" gatherLibraries
+
+# Can be used to manually add additional directories with shared object files
+# to be included for the next autoPatchelf invocation.
+addAutoPatchelfSearchPath() {
+    local -a findOpts=()
+
+    while [ $# -gt 0 ]; do
+        case "$1" in
+            --) shift; break;;
+            --no-recurse) shift; findOpts+=("-maxdepth" 1);;
+            --*)
+                echo "addAutoPatchelfSearchPath: ERROR: Invalid command line" \
+                     "argument: $1" >&2
+                return 1;;
+            *) break;;
+        esac
+    done
+
+    local dir=
+    while IFS= read -r -d '' dir; do
+        extraAutoPatchelfLibs+=("$dir")
+    done <  <(find "$@" "${findOpts[@]}" \! -type d \
+            \( -name '*.so' -o -name '*.so.*' \) -print0 \
+            | sed -z 's#/[^/]*$##' \
+            | uniq -z
+        )
+}
+
+
+autoPatchelf() {
+    local norecurse=
+    while [ $# -gt 0 ]; do
+        case "$1" in
+            --) shift; break;;
+            --no-recurse) shift; norecurse=1;;
+            --*)
+                echo "autoPatchelf: ERROR: Invalid command line" \
+                     "argument: $1" >&2
+                return 1;;
+            *) break;;
+        esac
+    done
+
+    if [ -n "$__structuredAttrs" ]; then
+        local ignoreMissingDepsArray=( "${autoPatchelfIgnoreMissingDeps[@]}" )
+        local appendRunpathsArray=( "${appendRunpaths[@]}" )
+        local runtimeDependenciesArray=( "${runtimeDependencies[@]}" )
+        local patchelfFlagsArray=( "${patchelfFlags[@]}" )
+    else
+        readarray -td' ' ignoreMissingDepsArray < <(echo -n "$autoPatchelfIgnoreMissingDeps")
+        local appendRunpathsArray=($appendRunpaths)
+        local runtimeDependenciesArray=($runtimeDependencies)
+        local patchelfFlagsArray=($patchelfFlags)
+    fi
+
+    # Check if ignoreMissingDepsArray contains "1" and if so, replace it with
+    # "*", printing a deprecation warning.
+    for dep in "${ignoreMissingDepsArray[@]}"; do
+        if [ "$dep" == "1" ]; then
+            echo "autoPatchelf: WARNING: setting 'autoPatchelfIgnoreMissingDeps" \
+                 "= true;' is deprecated and will be removed in a future release." \
+                 "Use 'autoPatchelfIgnoreMissingDeps = [ \"*\" ];' instead." >&2
+            ignoreMissingDepsArray=( "*" )
+            break
+        fi
+    done
+
+    @pythonInterpreter@ @autoPatchelfScript@                            \
+        ${norecurse:+--no-recurse}                                      \
+        --ignore-missing "${ignoreMissingDepsArray[@]}"                 \
+        --paths "$@"                                                    \
+        --libs "${autoPatchelfLibs[@]}"                                 \
+               "${extraAutoPatchelfLibs[@]}"                            \
+        --runtime-dependencies "${runtimeDependenciesArray[@]/%//lib}"  \
+        --append-rpaths "${appendRunpathsArray[@]}"                     \
+        --extra-args "${patchelfFlagsArray[@]}"
+}
+
+autoPatchelfPostFixup() {
+    # XXX: This should ultimately use fixupOutputHooks but we currently don't have
+    # a way to enforce the order. If we have $runtimeDependencies set, the setup
+    # hook of patchelf is going to ruin everything and strip out those additional
+    # RPATHs.
+    #
+    # So what we do here is basically run in postFixup and emulate the same
+    # behaviour as fixupOutputHooks because the setup hook for patchelf is run in
+    # fixupOutput and the postFixup hook runs later.
+    if [[ -z "${dontAutoPatchelf-}" ]]; then
+        autoPatchelf -- $(for output in $(getAllOutputNames); do
+            [ -e "${!output}" ] || continue
+            echo "${!output}"
+        done)
+    fi
+}
+
+postFixupHooks+=(autoPatchelfPostFixup)
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/autoreconf.sh b/nixpkgs/pkgs/build-support/setup-hooks/autoreconf.sh
new file mode 100644
index 000000000000..6ce879ac092d
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/autoreconf.sh
@@ -0,0 +1,7 @@
+preConfigurePhases="${preConfigurePhases:-} autoreconfPhase"
+
+autoreconfPhase() {
+    runHook preAutoreconf
+    autoreconf ${autoreconfFlags:---install --force --verbose}
+    runHook postAutoreconf
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/breakpoint-hook.sh b/nixpkgs/pkgs/build-support/setup-hooks/breakpoint-hook.sh
new file mode 100644
index 000000000000..6bef786ac3ac
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/breakpoint-hook.sh
@@ -0,0 +1,9 @@
+breakpointHook() {
+    local red='\033[0;31m'
+    local no_color='\033[0m'
+
+    echo -e "${red}build failed in ${curPhase} with exit code ${exitCode}${no_color}"
+    printf "To attach install cntr and run the following command as root:\n\n"
+    sh -c "echo '   cntr attach -t command cntr-${out}'; while true; do sleep 99999999; done"
+}
+failureHooks+=(breakpointHook)
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/compress-man-pages.sh b/nixpkgs/pkgs/build-support/setup-hooks/compress-man-pages.sh
new file mode 100644
index 000000000000..0d8a76558026
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/compress-man-pages.sh
@@ -0,0 +1,33 @@
+fixupOutputHooks+=('if [ -z "${dontGzipMan-}" ]; then compressManPages "$prefix"; fi')
+
+compressManPages() {
+    local dir="$1"
+
+    if [ -L "$dir"/share ] || [ -L "$dir"/share/man ] || [ ! -d "$dir/share/man" ]
+        then return
+    fi
+    echo "gzipping man pages under $dir/share/man/"
+
+    # Compress all uncompressed manpages.  Don't follow symlinks, etc.
+    find "$dir"/share/man/ -type f -a '!' -regex '.*\.\(bz2\|gz\|xz\)$' -print0 \
+        | while IFS= read -r -d $'\0' f
+    do
+        if gzip -c -n "$f" > "$f".gz; then
+            rm "$f"
+        else
+            rm "$f".gz
+        fi
+    done
+
+    # Point symlinks to compressed manpages.
+    find "$dir"/share/man/ -type l -a '!' -regex '.*\.\(bz2\|gz\|xz\)$' -print0 \
+        | sort -z \
+        | while IFS= read -r -d $'\0' f
+    do
+        local target
+        target="$(readlink -f "$f")"
+        if [ -f "$target".gz ]; then
+            ln -sf "$target".gz "$f".gz && rm "$f"
+        fi
+    done
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/copy-desktop-items.sh b/nixpkgs/pkgs/build-support/setup-hooks/copy-desktop-items.sh
new file mode 100644
index 000000000000..313ebc980344
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/copy-desktop-items.sh
@@ -0,0 +1,43 @@
+# shellcheck shell=bash
+
+# Setup hook that installs specified desktop items.
+#
+# Example usage in a derivation:
+#
+#   { …, makeDesktopItem, copyDesktopItems, … }:
+#
+#   let desktopItem = makeDesktopItem { … }; in
+#   stdenv.mkDerivation {
+#     …
+#     nativeBuildInputs = [ copyDesktopItems ];
+#
+#     desktopItems =  [ desktopItem ];
+#     …
+#   }
+#
+# This hook will copy files which are either given by full path
+# or all '*.desktop' files placed inside the 'share/applications'
+# folder of each `desktopItems` argument.
+
+postInstallHooks+=(copyDesktopItems)
+
+copyDesktopItems() {
+    if [ "${dontCopyDesktopItems-}" = 1 ]; then return; fi
+
+    if [ -z "$desktopItems" ]; then
+        return
+    fi
+
+    applications="${!outputBin}/share/applications"
+    for desktopItem in $desktopItems; do
+        if [[ -f "$desktopItem" ]]; then
+            echo "Copying '$desktopItem' into '${applications}'"
+            install -D -m 444 -t "${applications}" "$desktopItem"
+        else
+            for f in "$desktopItem"/share/applications/*.desktop; do
+                echo "Copying '$f' into '${applications}'"
+                install -D -m 444 -t "${applications}" "$f"
+            done
+        fi
+    done
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/copy-pkgconfig-items.sh b/nixpkgs/pkgs/build-support/setup-hooks/copy-pkgconfig-items.sh
new file mode 100644
index 000000000000..8c04ec9b5f0e
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/copy-pkgconfig-items.sh
@@ -0,0 +1,46 @@
+# shellcheck shell=bash
+
+# Setup hook that installs specified pkgconfig items.
+#
+# Example usage in a derivation:
+#
+#   { …, makePkgconfigItem, copyPkgconfigItems, … }:
+#
+#   let pkgconfigItem = makePkgconfigItem { … }; in
+#   stdenv.mkDerivation {
+#     …
+#     nativeBuildInputs = [ copyPkgconfigItems ];
+#
+#     pkgconfigItems =  [ pkgconfigItem ];
+#     …
+#   }
+#
+# This hook will copy files which are either given by full path
+# or all '*.pc' files placed inside the 'lib/pkgconfig'
+# folder of each `pkgconfigItems` argument.
+
+postInstallHooks+=(copyPkgconfigItems)
+
+copyPkgconfigItems() {
+    if [ "${dontCopyPkgconfigItems-}" = 1 ]; then return; fi
+
+    if [ -z "$pkgconfigItems" ]; then
+        return
+    fi
+
+    pkgconfigdir="${!outputDev}/lib/pkgconfig"
+    for pkgconfigItem in $pkgconfigItems; do
+        if [[ -f "$pkgconfigItem" ]]; then
+            substituteAllInPlace "$pkgconfigItem"
+            echo "Copying '$pkgconfigItem' into '${pkgconfigdir}'"
+            install -D -m 444 -t "${pkgconfigdir}" "$pkgconfigItem"
+            substituteAllInPlace "${pkgconfigdir}"/*
+        else
+            for f in "$pkgconfigItem"/lib/pkgconfig/*.pc; do
+                echo "Copying '$f' into '${pkgconfigdir}'"
+                install -D -m 444 -t "${pkgconfigdir}" "$f"
+                substituteAllInPlace "${pkgconfigdir}"/*
+            done
+        fi
+    done
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/desktop-to-darwin-bundle.sh b/nixpkgs/pkgs/build-support/setup-hooks/desktop-to-darwin-bundle.sh
new file mode 100644
index 000000000000..5b38f4376070
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/desktop-to-darwin-bundle.sh
@@ -0,0 +1,245 @@
+# shellcheck shell=bash
+fixupOutputHooks+=('convertDesktopFiles $prefix')
+
+# Get a param out of a desktop file. First parameter is the file and the second
+# is the key who's value we should fetch.
+getDesktopParam() {
+    local file="$1"
+    local key="$2"
+    local line k v
+
+    while read -r line; do
+        if [[ "$line" = *=* ]]; then
+            k="${line%%=*}"
+            v="${line#*=}"
+
+            if [[ "$k" = "$key" ]]; then
+                echo "$v"
+                return
+            fi
+        fi
+    done < "$file"
+
+    return 1
+}
+
+# Convert a freedesktop.org icon theme for a given app to a .icns file. When possible, missing
+# icons are synthesized from SVG or rescaled from existing ones (when within the size threshold).
+convertIconTheme() {
+    local -r out=$1
+    local -r sharePath=$2
+    local -r iconName=$3
+    local -r theme=${4:-hicolor}
+
+    # Sizes based on archived Apple documentation:
+    # https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/app-icon#app-icon-sizes
+    local -ra iconSizes=(16 32 128 256 512)
+    local -ra scales=([1]="" [2]="@2")
+
+    # Based loosely on the algorithm at:
+    # https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#icon_lookup
+    # Assumes threshold = 2 for ease of implementation.
+    function findIcon() {
+        local -r iconSize=$1
+        local -r scale=$2
+
+        local scaleSuffix=${scales[$scale]}
+        local exactSize=${iconSize}x${iconSize}${scaleSuffix}
+
+        local -a validSizes=(
+            ${exactSize}
+            $((iconSize + 1))x$((iconSize + 1))${scaleSuffix}
+            $((iconSize + 2))x$((iconSize + 2))${scaleSuffix}
+            $((iconSize - 1))x$((iconSize - 1))${scaleSuffix}
+            $((iconSize - 2))x$((iconSize - 2))${scaleSuffix}
+        )
+
+        local fallbackIcon=
+
+        for iconIndex in "${!candidateIcons[@]}"; do
+            for maybeSize in "${validSizes[@]}"; do
+                icon=${candidateIcons[$iconIndex]}
+                if [[ $icon = */$maybeSize/* ]]; then
+                    if [[ $maybeSize = $exactSize ]]; then
+                        echo "fixed $icon"
+                        return 0
+                    else
+                        echo "threshold $icon"
+                        return 0
+                    fi
+                elif [[ -a $icon && -z "$fallbackIcon" ]]; then
+                    fallbackIcon="$icon"
+                fi
+            done
+        done
+
+        if [[ -n "$fallbackIcon" ]]; then
+            echo "fallback $fallbackIcon"
+            return 0
+        fi
+
+        echo "scalable"
+    }
+
+    function resizeIcon() {
+        local -r in=$1
+        local -r out=$2
+        local -r iconSize=$3
+        local -r scale=$4
+
+        local density=$((72 * scale))x$((72 * scale))
+        local dim=$((iconSize * scale))
+
+        echo "desktopToDarwinBundle: resizing icon $in to $out, size $dim" >&2
+        magick convert -scale "${dim}x${dim}" -density "$density" -units PixelsPerInch "$in" "$out"
+    }
+
+    function synthesizeIcon() {
+        local -r in=$1
+        local -r out=$2
+        local -r iconSize=$3
+        local -r scale=$4
+
+        if [[ $in != '-' ]]; then
+            local density=$((72 * scale))x$((72 * scale))
+            local dim=$((iconSize * scale))
+
+            echo "desktopToDarwinBundle: rasterizing svg $in to $out, size $dim" >&2
+            rsvg-convert --keep-aspect-ratio --width "$dim" --height "$dim" "$in" --output "$out"
+            magick convert -density "$density" -units PixelsPerInch "$out" "$out"
+        else
+            return 1
+        fi
+    }
+
+    function getIcons() {
+        local -r sharePath=$1
+        local -r iconname=$2
+        local -r theme=$3
+        local -r resultdir=$(mktemp -d)
+
+        local -ar candidateIcons=(
+            "${sharePath}/icons/${theme}/"*"/${iconname}.png"
+            "${sharePath}/icons/${theme}/"*"/${iconname}.xpm"
+        )
+
+        local -a scalableIcon=("${sharePath}/icons/${theme}/scalable/${iconname}.svg"*)
+        if [[ ${#scalableIcon[@]} = 0 ]]; then
+            scalableIcon=('-')
+        fi
+
+        # Tri-state variable, NONE means no icons have been found, an empty
+        # icns file will be generated, not sure that's necessary because macOS
+        # will default to a generic icon if no icon can be found.
+        #
+        # OTHER means an appropriate icon was found.
+        #
+        # Any other value is a path to an icon file that isn't scalable or
+        # within the threshold. This is used as a fallback in case no better
+        # icon can be found and will be scaled as much as
+        # necessary to result in appropriate icon sizes.
+        local foundIcon=NONE
+        for iconSize in "${iconSizes[@]}"; do
+            for scale in "${!scales[@]}"; do
+                local iconResult=$(findIcon $iconSize $scale)
+                local type=${iconResult%% *}
+                local icon=${iconResult#* }
+                local scaleSuffix=${scales[$scale]}
+                local result=${resultdir}/${iconSize}x${iconSize}${scales[$scale]}${scaleSuffix:+x}.png
+                echo "desktopToDarwinBundle: using $type icon $icon for size $iconSize$scaleSuffix" >&2
+                case $type in
+                    fixed)
+                        local density=$((72 * scale))x$((72 * scale))
+                        magick convert -density "$density" -units PixelsPerInch "$icon" "$result"
+                        foundIcon=OTHER
+                        ;;
+                    threshold)
+                        # Synthesize an icon of the exact size if a scalable icon is available
+                        # instead of scaling one and ending up with a fuzzy icon.
+                        if ! synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale"; then
+                            resizeIcon "$icon" "$result" "$iconSize" "$scale"
+                        fi
+                        foundIcon=OTHER
+                        ;;
+                    scalable)
+                        synthesizeIcon "${scalableIcon[0]}" "$result" "$iconSize" "$scale" || true
+                        foundIcon=OTHER
+                        ;;
+                    fallback)
+                        # Use the largest size available to scale to
+                        # appropriate sizes.
+                        if [[ $foundIcon != OTHER ]]; then
+                          foundIcon=$icon
+                        fi
+                        ;;
+                    *)
+                        ;;
+                esac
+            done
+        done
+        if [[ $foundIcon != NONE && $foundIcon != OTHER ]]; then
+            # Ideally we'd only resize to whatever the closest sizes are,
+            # starting from whatever icon sizes are available.
+            for iconSize in 16 32 128 256 512; do
+              local result=${resultdir}/${iconSize}x${iconSize}.png
+              resizeIcon "$foundIcon" "$result" "$iconSize" 1
+            done
+        fi
+        echo "$resultdir"
+    }
+
+    iconsdir=$(getIcons "$sharePath" "apps/${iconName}" "$theme")
+    if [[ -n "$(ls -A1 "$iconsdir")" ]]; then
+        icnsutil compose --toc "$out/${iconName}.icns" "$iconsdir/"*
+    else
+        echo "Warning: no icons were found. Creating an empty icon for ${iconName}.icns."
+        touch "$out/${iconName}.icns"
+    fi
+}
+
+processExecFieldCodes() {
+  local -r file=$1
+  local -r execRaw=$(getDesktopParam "${file}" "Exec")
+  local -r execNoK="${execRaw/\%k/${file}}"
+  local -r execNoKC="${execNoK/\%c/$(getDesktopParam "${file}" "Name")}"
+  local -r icon=$(getDesktopParam "${file}" "Icon")
+  local -r execNoKCI="${execNoKC/\%i/${icon:+--icon }${icon}}"
+  local -r execNoKCIfu="${execNoKCI/ \%[fu]/}"
+  local -r exec="${execNoKCIfu/ \%[FU]/}"
+  if [[ "$exec" != "$execRaw" ]]; then
+    echo 1>&2 "desktopToDarwinBundle: Application bundles do not understand desktop entry field codes. Changed '$execRaw' to '$exec'."
+  fi
+  echo "$exec"
+}
+
+# For a given .desktop file, generate a darwin '.app' bundle for it.
+convertDesktopFile() {
+    local -r file=$1
+    local -r sharePath=$(dirname "$(dirname "$file")")
+    local -r name=$(getDesktopParam "${file}" "Name")
+    local -r macOSExec=$(getDesktopParam "${file}" "X-macOS-Exec")
+    if [[ "$macOSExec" ]]; then
+      local -r exec="$macOSExec"
+    else
+      local -r exec=$(processExecFieldCodes "${file}")
+    fi
+    local -r iconName=$(getDesktopParam "${file}" "Icon")
+    local -r squircle=$(getDesktopParam "${file}" "X-macOS-SquircleIcon")
+
+    mkdir -p "${!outputBin}/Applications/${name}.app/Contents/MacOS"
+    mkdir -p "${!outputBin}/Applications/${name}.app/Contents/Resources"
+
+    convertIconTheme "${!outputBin}/Applications/${name}.app/Contents/Resources" "$sharePath" "$iconName"
+
+    write-darwin-bundle "${!outputBin}" "$name" "$exec" "$iconName" "$squircle"
+}
+
+convertDesktopFiles() {
+    local dir="$1/share/applications/"
+
+    if [ -d "${dir}" ]; then
+        for desktopFile in $(find "$dir" -iname "*.desktop"); do
+            convertDesktopFile "$desktopFile";
+        done
+    fi
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/die.sh b/nixpkgs/pkgs/build-support/setup-hooks/die.sh
new file mode 100644
index 000000000000..0db41e030f4c
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/die.sh
@@ -0,0 +1,21 @@
+# Exit with backtrace and error message
+#
+# Usage: die "Error message"
+die() {
+    # Let us be a little sloppy with errors, because otherwise the final
+    # invocation of `caller` below will cause the script to exit.
+    set +e
+
+    # Print our error message
+    printf "\nBuilder called die: %b\n" "$*"
+    printf "Backtrace:\n"
+
+    # Print a backtrace.
+    local frame=0
+    while caller $frame; do
+        ((frame++));
+    done
+    printf "\n"
+
+    exit 1
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/enable-coverage-instrumentation.sh b/nixpkgs/pkgs/build-support/setup-hooks/enable-coverage-instrumentation.sh
new file mode 100644
index 000000000000..2b48fea4ff0b
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/enable-coverage-instrumentation.sh
@@ -0,0 +1,20 @@
+postPhases+=" cleanupBuildDir"
+
+# Force GCC to build with coverage instrumentation.  Also disable
+# optimisation, since it may confuse things.
+export NIX_CFLAGS_COMPILE="${NIX_CFLAGS_COMPILE:-} -O0 --coverage"
+
+# Get rid of everything that isn't a gcno file or a C source file.
+# Also strip the `.tmp_' prefix from gcno files.  (The Linux kernel
+# creates these.)
+cleanupBuildDir() {
+    if ! [ -e $out/.build ]; then return; fi
+
+    find $out/.build/ -type f -a ! \
+        \( -name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.h" -o -name "*.hh" -o -name "*.y" -o -name "*.l" -o -name "*.gcno" \) \
+        | xargs rm -f --
+
+    for i in $(find $out/.build/ -name ".tmp_*.gcno"); do
+        mv "$i" "$(echo $i | sed s/.tmp_//)"
+    done
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/find-xml-catalogs.sh b/nixpkgs/pkgs/build-support/setup-hooks/find-xml-catalogs.sh
new file mode 100644
index 000000000000..f446a6f27fd9
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/find-xml-catalogs.sh
@@ -0,0 +1,22 @@
+addXMLCatalogs () {
+    local d i
+    # ‘xml/dtd’ and ‘xml/xsl’ are deprecated. Catalogs should be
+    # installed underneath ‘share/xml’.
+    for d in $1/share/xml $1/xml/dtd $1/xml/xsl; do
+        if [ -d $d ]; then
+            for i in $(find $d -name catalog.xml); do
+                XML_CATALOG_FILES+=" $i"
+            done
+        fi
+    done
+}
+
+if [ -z "${libxmlHookDone-}" ]; then
+    libxmlHookDone=1
+
+    # Set up XML_CATALOG_FILES.  An empty initial value prevents
+    # xmllint and xsltproc from looking in /etc/xml/catalog.
+    export XML_CATALOG_FILES=''
+    if [ -z "$XML_CATALOG_FILES" ]; then XML_CATALOG_FILES=" "; fi
+    addEnvHooks "$hostOffset" addXMLCatalogs
+fi
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/fix-darwin-dylib-names.sh b/nixpkgs/pkgs/build-support/setup-hooks/fix-darwin-dylib-names.sh
new file mode 100644
index 000000000000..e103fe77d9be
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/fix-darwin-dylib-names.sh
@@ -0,0 +1,40 @@
+# On macOS, binaries refer to dynamic library dependencies using
+# either relative paths (e.g. "libicudata.dylib", searched relative to
+# $DYLD_LIBRARY_PATH) or absolute paths
+# (e.g. "/nix/store/.../lib/libicudata.dylib").  In Nix, the latter is
+# preferred since it allows programs to just work.  When linking
+# against a library (e.g. "-licudata"), the linker uses the install
+# name embedded in the dylib (which can be shown using "otool -D").
+# Most packages create dylibs with absolute install names, but some do
+# not.  This setup hook fixes dylibs by setting their install names to
+# their absolute path (using "install_name_tool -id").  It also
+# rewrites references in other dylibs to absolute paths.
+
+fixupOutputHooks+=('fixDarwinDylibNamesIn $prefix')
+
+fixDarwinDylibNames() {
+    local flags=()
+    local old_id
+
+    for fn in "$@"; do
+        flags+=(-change "$(basename "$fn")" "$fn")
+    done
+
+    for fn in "$@"; do
+        if [ -L "$fn" ]; then continue; fi
+        echo "$fn: fixing dylib"
+        int_out=$(@targetPrefix@install_name_tool -id "$fn" "${flags[@]}" "$fn" 2>&1)
+        result=$?
+        if [ "$result" -ne 0 ] &&
+            ! grep "shared library stub file and can't be changed" <<< "$out"
+        then
+            echo "$int_out" >&2
+            exit "$result"
+        fi
+    done
+}
+
+fixDarwinDylibNamesIn() {
+    local dir="$1"
+    fixDarwinDylibNames $(find "$dir" -name "*.dylib" -o -name "*.so" -o -name "*.so.*")
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/gog-unpack.sh b/nixpkgs/pkgs/build-support/setup-hooks/gog-unpack.sh
new file mode 100644
index 000000000000..559b543fadfc
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/gog-unpack.sh
@@ -0,0 +1,11 @@
+unpackPhase="unpackGog"
+
+unpackGog() {
+    runHook preUnpackGog
+
+    innoextract --silent --extract --exclude-temp "${src}"
+
+    find . -depth -print -execdir rename -f 'y/A-Z/a-z/' '{}' \;
+
+    runHook postUnpackGog
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/install-shell-files.sh b/nixpkgs/pkgs/build-support/setup-hooks/install-shell-files.sh
new file mode 100644
index 000000000000..194b408b1050
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/install-shell-files.sh
@@ -0,0 +1,230 @@
+# shellcheck shell=bash
+# Setup hook for the `installShellFiles` package.
+#
+# Example usage in a derivation:
+#
+#   { …, installShellFiles, … }:
+#   stdenv.mkDerivation {
+#     …
+#     nativeBuildInputs = [ installShellFiles ];
+#     postInstall = ''
+#       installManPage share/doc/foobar.1
+#       installShellCompletion share/completions/foobar.{bash,fish,zsh}
+#     '';
+#     …
+#   }
+#
+# See comments on each function for more details.
+
+# installManPage <path> [...<path>]
+#
+# Each argument is checked for its man section suffix and installed into the appropriate
+# share/man/man<n>/ directory. The function returns an error if any paths don't have the man
+# section suffix (with optional .gz compression).
+installManPage() {
+    local path
+    for path in "$@"; do
+        if (( "${NIX_DEBUG:-0}" >= 1 )); then
+            echo "installManPage: installing $path"
+        fi
+        if test -z "$path"; then
+            echo "installManPage: error: path cannot be empty" >&2
+            return 1
+        fi
+        local basename
+        basename=$(stripHash "$path") # use stripHash in case it's a nix store path
+        local trimmed=${basename%.gz} # don't get fooled by compressed manpages
+        local suffix=${trimmed##*.}
+        if test -z "$suffix" -o "$suffix" = "$trimmed"; then
+            echo "installManPage: error: path missing manpage section suffix: $path" >&2
+            return 1
+        fi
+        local outRoot
+        if test "$suffix" = 3; then
+            outRoot=${!outputDevman:?}
+        else
+            outRoot=${!outputMan:?}
+        fi
+        install -Dm644 -T "$path" "${outRoot}/share/man/man$suffix/$basename" || return
+    done
+}
+
+# installShellCompletion [--cmd <name>] ([--bash|--fish|--zsh] [--name <name>] <path>)...
+#
+# Each path is installed into the appropriate directory for shell completions for the given shell.
+# If one of `--bash`, `--fish`, or `--zsh` is given the path is assumed to belong to that shell.
+# Otherwise the file extension will be examined to pick a shell. If the shell is unknown a warning
+# will be logged and the command will return a non-zero status code after processing any remaining
+# paths. Any of the shell flags will affect all subsequent paths (unless another shell flag is
+# given).
+#
+# If the shell completion needs to be renamed before installing the optional `--name <name>` flag
+# may be given. Any name provided with this flag only applies to the next path.
+#
+# If all shell completions need to be renamed before installing the optional `--cmd <name>` flag
+# may be given. This will synthesize a name for each file, unless overridden with an explicit
+# `--name` flag. For example, `--cmd foobar` will synthesize the name `_foobar` for zsh and
+# `foobar.bash` for bash.
+#
+# For zsh completions, if the `--name` flag is not given, the path will be automatically renamed
+# such that `foobar.zsh` becomes `_foobar`.
+#
+# A path may be a named fd, such as produced by the bash construct `<(cmd)`. When using a named fd,
+# the shell type flag must be provided, and either the `--name` or `--cmd` flag must be provided.
+# This might look something like:
+#
+#   installShellCompletion --zsh --name _foobar <($out/bin/foobar --zsh-completion)
+#
+# This command accepts multiple shell flags in conjunction with multiple paths if you wish to
+# install them all in one command:
+#
+#   installShellCompletion share/completions/foobar.{bash,fish} --zsh share/completions/_foobar
+#
+# However it may be easier to read if each shell is split into its own invocation, especially when
+# renaming is involved:
+#
+#   installShellCompletion --bash --name foobar.bash share/completions.bash
+#   installShellCompletion --fish --name foobar.fish share/completions.fish
+#   installShellCompletion --zsh --name _foobar share/completions.zsh
+#
+# Or to use shell newline escaping to split a single invocation across multiple lines:
+#
+#   installShellCompletion --cmd foobar \
+#     --bash <($out/bin/foobar --bash-completion) \
+#     --fish <($out/bin/foobar --fish-completion) \
+#     --zsh <($out/bin/foobar --zsh-completion)
+#
+# If any argument is `--` the remaining arguments will be treated as paths.
+installShellCompletion() {
+    local shell='' name='' cmdname='' retval=0 parseArgs=1 arg
+    while { arg=$1; shift; }; do
+        # Parse arguments
+        if (( parseArgs )); then
+            case "$arg" in
+            --bash|--fish|--zsh)
+                shell=${arg#--}
+                continue;;
+            --name)
+                name=$1
+                shift || {
+                    echo 'installShellCompletion: error: --name flag expected an argument' >&2
+                    return 1
+                }
+                continue;;
+            --name=*)
+                # treat `--name=foo` the same as `--name foo`
+                name=${arg#--name=}
+                continue;;
+            --cmd)
+                cmdname=$1
+                shift || {
+                    echo 'installShellCompletion: error: --cmd flag expected an argument' >&2
+                    return 1
+                }
+                continue;;
+            --cmd=*)
+                # treat `--cmd=foo` the same as `--cmd foo`
+                cmdname=${arg#--cmd=}
+                continue;;
+            --?*)
+                echo "installShellCompletion: warning: unknown flag ${arg%%=*}" >&2
+                retval=2
+                continue;;
+            --)
+                # treat remaining args as paths
+                parseArgs=0
+                continue;;
+            esac
+        fi
+        if (( "${NIX_DEBUG:-0}" >= 1 )); then
+            echo "installShellCompletion: installing $arg${name:+ as $name}"
+        fi
+        # if we get here, this is a path or named pipe
+        # Identify shell and output name
+        local curShell=$shell
+        local outName=''
+        if [[ -z "$arg" ]]; then
+            echo "installShellCompletion: error: empty path is not allowed" >&2
+            return 1
+        elif [[ -p "$arg" ]]; then
+            # this is a named fd or fifo
+            if [[ -z "$curShell" ]]; then
+                echo "installShellCompletion: error: named pipe requires one of --bash, --fish, or --zsh" >&2
+                return 1
+            elif [[ -z "$name" && -z "$cmdname" ]]; then
+                echo "installShellCompletion: error: named pipe requires one of --cmd or --name" >&2
+                return 1
+            fi
+        else
+            # this is a path
+            local argbase
+            argbase=$(stripHash "$arg")
+            if [[ -z "$curShell" ]]; then
+                # auto-detect the shell
+                case "$argbase" in
+                ?*.bash) curShell=bash;;
+                ?*.fish) curShell=fish;;
+                ?*.zsh) curShell=zsh;;
+                *)
+                    if [[ "$argbase" = _* && "$argbase" != *.* ]]; then
+                        # probably zsh
+                        echo "installShellCompletion: warning: assuming path \`$arg' is zsh; please specify with --zsh" >&2
+                        curShell=zsh
+                    else
+                        echo "installShellCompletion: warning: unknown shell for path: $arg" >&2
+                        retval=2
+                        continue
+                    fi;;
+                esac
+            fi
+            outName=$argbase
+        fi
+        # Identify output path
+        if [[ -n "$name" ]]; then
+            outName=$name
+        elif [[ -n "$cmdname" ]]; then
+            case "$curShell" in
+            bash|fish) outName=$cmdname.$curShell;;
+            zsh) outName=_$cmdname;;
+            *)
+                # Our list of shells is out of sync with the flags we accept or extensions we detect.
+                echo 'installShellCompletion: internal error' >&2
+                return 1;;
+            esac
+        fi
+        local sharePath
+        case "$curShell" in
+        bash) sharePath=bash-completion/completions;;
+        fish) sharePath=fish/vendor_completions.d;;
+        zsh)
+            sharePath=zsh/site-functions
+            # only apply automatic renaming if we didn't have a manual rename
+            if [[ -z "$name" && -z "$cmdname" ]]; then
+                # convert a name like `foo.zsh` into `_foo`
+                outName=${outName%.zsh}
+                outName=_${outName#_}
+            fi;;
+        *)
+            # Our list of shells is out of sync with the flags we accept or extensions we detect.
+            echo 'installShellCompletion: internal error' >&2
+            return 1;;
+        esac
+        # Install file
+        local outDir="${!outputBin:?}/share/$sharePath"
+        local outPath="$outDir/$outName"
+        if [[ -p "$arg" ]]; then
+            # install handles named pipes on NixOS but not on macOS
+            mkdir -p "$outDir" \
+            && cat "$arg" > "$outPath"
+        else
+            install -Dm644 -T "$arg" "$outPath"
+        fi || return
+        # Clear the per-path flags
+        name=
+    done
+    if [[ -n "$name" ]]; then
+        echo 'installShellCompletion: error: --name flag given with no path' >&2
+        return 1
+    fi
+    return $retval
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/keep-build-tree.sh b/nixpkgs/pkgs/build-support/setup-hooks/keep-build-tree.sh
new file mode 100644
index 000000000000..754900bfc337
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/keep-build-tree.sh
@@ -0,0 +1,6 @@
+prePhases+=" moveBuildDir"
+
+moveBuildDir() {
+    mkdir -p $out/.build
+    cd $out/.build
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/ld-is-cc-hook.sh b/nixpkgs/pkgs/build-support/setup-hooks/ld-is-cc-hook.sh
new file mode 100644
index 000000000000..b53e184b0956
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/ld-is-cc-hook.sh
@@ -0,0 +1,5 @@
+ld-is-cc-hook() {
+    LD=$CC
+}
+
+preConfigureHooks+=(ld-is-cc-hook)
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/make-binary-wrapper/default.nix b/nixpkgs/pkgs/build-support/setup-hooks/make-binary-wrapper/default.nix
new file mode 100644
index 000000000000..f364dd5de753
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/make-binary-wrapper/default.nix
@@ -0,0 +1,27 @@
+{ targetPackages
+, lib
+, makeSetupHook
+, dieHook
+, writeShellScript
+, tests
+, cc ? targetPackages.stdenv.cc
+, sanitizers ? []
+}:
+
+makeSetupHook {
+  name = "make-binary-wrapper-hook";
+  propagatedBuildInputs = [ dieHook ];
+
+  substitutions = {
+    cc = "${cc}/bin/${cc.targetPrefix}cc ${lib.escapeShellArgs (map (s: "-fsanitize=${s}") sanitizers)}";
+  };
+
+  passthru = {
+    # Extract the function call used to create a binary wrapper from its embedded docstring
+    extractCmd = writeShellScript "extract-binary-wrapper-cmd" ''
+      ${cc.bintools.targetPrefix}strings -dw "$1" | sed -n '/^makeCWrapper/,/^$/ p'
+    '';
+
+    tests = tests.makeBinaryWrapper;
+  };
+} ./make-binary-wrapper.sh
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/make-binary-wrapper/make-binary-wrapper.sh b/nixpkgs/pkgs/build-support/setup-hooks/make-binary-wrapper/make-binary-wrapper.sh
new file mode 100644
index 000000000000..3948342a36fc
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/make-binary-wrapper/make-binary-wrapper.sh
@@ -0,0 +1,471 @@
+
+set -euo pipefail
+
+# Assert that FILE exists and is executable
+#
+# assertExecutable FILE
+assertExecutable() {
+    local file="$1"
+    [[ -f "$file" && -x "$file" ]] || \
+        die "Cannot wrap '$file' because it is not an executable file"
+}
+
+# Generate a binary executable wrapper for wrapping an executable.
+# The binary is compiled from generated C-code using gcc.
+# makeWrapper EXECUTABLE OUT_PATH ARGS
+
+# ARGS:
+# --argv0        NAME    : set the name of the executed process to NAME
+#                          (if unset or empty, defaults to EXECUTABLE)
+# --inherit-argv0        : the executable inherits argv0 from the wrapper.
+#                          (use instead of --argv0 '$0')
+# --resolve-argv0        : if argv0 doesn't include a / character, resolve it against PATH
+# --set          VAR VAL : add VAR with value VAL to the executable's environment
+# --set-default  VAR VAL : like --set, but only adds VAR if not already set in
+#                          the environment
+# --unset        VAR     : remove VAR from the environment
+# --chdir        DIR     : change working directory (use instead of --run "cd DIR")
+# --add-flags    ARGS    : prepend ARGS to the invocation of the executable
+#                          (that is, *before* any arguments passed on the command line)
+# --append-flags ARGS    : append ARGS to the invocation of the executable
+#                          (that is, *after* any arguments passed on the command line)
+
+# --prefix          ENV SEP VAL   : suffix/prefix ENV with VAL, separated by SEP
+# --suffix
+
+# To troubleshoot a binary wrapper after you compiled it,
+# use the `strings` command or open the binary file in a text editor.
+makeWrapper() { makeBinaryWrapper "$@"; }
+makeBinaryWrapper() {
+    local NIX_CFLAGS_COMPILE= NIX_CFLAGS_LINK=
+    local original="$1"
+    local wrapper="$2"
+    shift 2
+
+    assertExecutable "$original"
+
+    mkdir -p "$(dirname "$wrapper")"
+
+    makeDocumentedCWrapper "$original" "$@" | \
+      @cc@ \
+        -Wall -Werror -Wpedantic \
+        -Wno-overlength-strings \
+        -Os \
+        -x c \
+        -o "$wrapper" -
+}
+
+# Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...>
+wrapProgram() { wrapProgramBinary "$@"; }
+wrapProgramBinary() {
+    local prog="$1"
+    local hidden
+
+    assertExecutable "$prog"
+
+    hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped
+    while [ -e "$hidden" ]; do
+      hidden="${hidden}_"
+    done
+    mv "$prog" "$hidden"
+    makeBinaryWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}"
+}
+
+# Generate source code for the wrapper in such a way that the wrapper inputs
+# will still be readable even after compilation
+# makeDocumentedCWrapper EXECUTABLE ARGS
+# ARGS: same as makeWrapper
+makeDocumentedCWrapper() {
+    local src docs
+    src=$(makeCWrapper "$@")
+    docs=$(docstring "$@")
+    printf '%s\n\n' "$src"
+    printf '%s\n' "$docs"
+}
+
+# makeCWrapper EXECUTABLE ARGS
+# ARGS: same as makeWrapper
+makeCWrapper() {
+    local argv0 inherit_argv0 n params cmd main flagsBefore flagsAfter flags executable length
+    local uses_prefix uses_suffix uses_assert uses_assert_success uses_stdio uses_asprintf
+    local resolve_path
+    executable=$(escapeStringLiteral "$1")
+    params=("$@")
+    length=${#params[*]}
+    for ((n = 1; n < length; n += 1)); do
+        p="${params[n]}"
+        case $p in
+            --set)
+                cmd=$(setEnv "${params[n + 1]}" "${params[n + 2]}")
+                main="$main$cmd"$'\n'
+                n=$((n + 2))
+                [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n'
+            ;;
+            --set-default)
+                cmd=$(setDefaultEnv "${params[n + 1]}" "${params[n + 2]}")
+                main="$main$cmd"$'\n'
+                uses_stdio=1
+                uses_assert_success=1
+                n=$((n + 2))
+                [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n'
+            ;;
+            --unset)
+                cmd=$(unsetEnv "${params[n + 1]}")
+                main="$main$cmd"$'\n'
+                uses_stdio=1
+                uses_assert_success=1
+                n=$((n + 1))
+                [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
+            ;;
+            --prefix)
+                cmd=$(setEnvPrefix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}")
+                main="$main$cmd"$'\n'
+                uses_prefix=1
+                uses_asprintf=1
+                uses_stdio=1
+                uses_assert_success=1
+                uses_assert=1
+                n=$((n + 3))
+                [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n'
+            ;;
+            --suffix)
+                cmd=$(setEnvSuffix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}")
+                main="$main$cmd"$'\n'
+                uses_suffix=1
+                uses_asprintf=1
+                uses_stdio=1
+                uses_assert_success=1
+                uses_assert=1
+                n=$((n + 3))
+                [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n'
+            ;;
+            --chdir)
+                cmd=$(changeDir "${params[n + 1]}")
+                main="$main$cmd"$'\n'
+                uses_stdio=1
+                uses_assert_success=1
+                n=$((n + 1))
+                [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
+            ;;
+            --add-flags)
+                flags="${params[n + 1]}"
+                flagsBefore="$flagsBefore $flags"
+                uses_assert=1
+                n=$((n + 1))
+                [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
+            ;;
+            --append-flags)
+                flags="${params[n + 1]}"
+                flagsAfter="$flagsAfter $flags"
+                uses_assert=1
+                n=$((n + 1))
+                [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
+            ;;
+            --argv0)
+                argv0=$(escapeStringLiteral "${params[n + 1]}")
+                inherit_argv0=
+                n=$((n + 1))
+                [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
+            ;;
+            --inherit-argv0)
+                # Whichever comes last of --argv0 and --inherit-argv0 wins
+                inherit_argv0=1
+            ;;
+            --resolve-argv0)
+                # this gets processed after other argv0 flags
+                uses_stdio=1
+                uses_string=1
+                resolve_argv0=1
+            ;;
+            *) # Using an error macro, we will make sure the compiler gives an understandable error message
+                main="$main#error makeCWrapper: Unknown argument ${p}"$'\n'
+            ;;
+        esac
+    done
+    [[ -z "$flagsBefore" && -z "$flagsAfter" ]] || main="$main"${main:+$'\n'}$(addFlags "$flagsBefore" "$flagsAfter")$'\n'$'\n'
+    [ -z "$inherit_argv0" ] && main="${main}argv[0] = \"${argv0:-${executable}}\";"$'\n'
+    [ -z "$resolve_argv0" ] || main="${main}argv[0] = resolve_argv0(argv[0]);"$'\n'
+    main="${main}return execv(\"${executable}\", argv);"$'\n'
+
+    [ -z "$uses_asprintf" ] || printf '%s\n' "#define _GNU_SOURCE         /* See feature_test_macros(7) */"
+    printf '%s\n' "#include <unistd.h>"
+    printf '%s\n' "#include <stdlib.h>"
+    [ -z "$uses_assert" ]   || printf '%s\n' "#include <assert.h>"
+    [ -z "$uses_stdio" ]    || printf '%s\n' "#include <stdio.h>"
+    [ -z "$uses_string" ]   || printf '%s\n' "#include <string.h>"
+    [ -z "$uses_assert_success" ] || printf '\n%s\n' "#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0)"
+    [ -z "$uses_prefix" ] || printf '\n%s\n' "$(setEnvPrefixFn)"
+    [ -z "$uses_suffix" ] || printf '\n%s\n' "$(setEnvSuffixFn)"
+    [ -z "$resolve_argv0" ] || printf '\n%s\n' "$(resolveArgv0Fn)"
+    printf '\n%s' "int main(int argc, char **argv) {"
+    printf '\n%s' "$(indent4 "$main")"
+    printf '\n%s\n' "}"
+}
+
+addFlags() {
+    local n flag before after var
+
+    # Disable file globbing, since bash will otherwise try to find
+    # filenames matching the the value to be prefixed/suffixed if
+    # it contains characters considered wildcards, such as `?` and
+    # `*`. We want the value as is, except we also want to split
+    # it on on the separator; hence we can't quote it.
+    local reenableGlob=0
+    if [[ ! -o noglob ]]; then
+        reenableGlob=1
+    fi
+    set -o noglob
+    # shellcheck disable=SC2086
+    before=($1) after=($2)
+    if (( reenableGlob )); then
+        set +o noglob
+    fi
+
+    var="argv_tmp"
+    printf '%s\n' "char **$var = calloc(${#before[@]} + argc + ${#after[@]} + 1, sizeof(*$var));"
+    printf '%s\n' "assert($var != NULL);"
+    printf '%s\n' "${var}[0] = argv[0];"
+    for ((n = 0; n < ${#before[@]}; n += 1)); do
+        flag=$(escapeStringLiteral "${before[n]}")
+        printf '%s\n' "${var}[$((n + 1))] = \"$flag\";"
+    done
+    printf '%s\n' "for (int i = 1; i < argc; ++i) {"
+    printf '%s\n' "    ${var}[${#before[@]} + i] = argv[i];"
+    printf '%s\n' "}"
+    for ((n = 0; n < ${#after[@]}; n += 1)); do
+        flag=$(escapeStringLiteral "${after[n]}")
+        printf '%s\n' "${var}[${#before[@]} + argc + $n] = \"$flag\";"
+    done
+    printf '%s\n' "${var}[${#before[@]} + argc + ${#after[@]}] = NULL;"
+    printf '%s\n' "argv = $var;"
+}
+
+# chdir DIR
+changeDir() {
+    local dir
+    dir=$(escapeStringLiteral "$1")
+    printf '%s' "assert_success(chdir(\"$dir\"));"
+}
+
+# prefix ENV SEP VAL
+setEnvPrefix() {
+    local env sep val
+    env=$(escapeStringLiteral "$1")
+    sep=$(escapeStringLiteral "$2")
+    val=$(escapeStringLiteral "$3")
+    printf '%s' "set_env_prefix(\"$env\", \"$sep\", \"$val\");"
+    assertValidEnvName "$1"
+}
+
+# suffix ENV SEP VAL
+setEnvSuffix() {
+    local env sep val
+    env=$(escapeStringLiteral "$1")
+    sep=$(escapeStringLiteral "$2")
+    val=$(escapeStringLiteral "$3")
+    printf '%s' "set_env_suffix(\"$env\", \"$sep\", \"$val\");"
+    assertValidEnvName "$1"
+}
+
+# setEnv KEY VALUE
+setEnv() {
+    local key value
+    key=$(escapeStringLiteral "$1")
+    value=$(escapeStringLiteral "$2")
+    printf '%s' "putenv(\"$key=$value\");"
+    assertValidEnvName "$1"
+}
+
+# setDefaultEnv KEY VALUE
+setDefaultEnv() {
+    local key value
+    key=$(escapeStringLiteral "$1")
+    value=$(escapeStringLiteral "$2")
+    printf '%s' "assert_success(setenv(\"$key\", \"$value\", 0));"
+    assertValidEnvName "$1"
+}
+
+# unsetEnv KEY
+unsetEnv() {
+    local key
+    key=$(escapeStringLiteral "$1")
+    printf '%s' "assert_success(unsetenv(\"$key\"));"
+    assertValidEnvName "$1"
+}
+
+# Makes it safe to insert STRING within quotes in a C String Literal.
+# escapeStringLiteral STRING
+escapeStringLiteral() {
+    local result
+    result=${1//$'\\'/$'\\\\'}
+    result=${result//\"/'\"'}
+    result=${result//$'\n'/"\n"}
+    result=${result//$'\r'/"\r"}
+    printf '%s' "$result"
+}
+
+# Indents every non-empty line by 4 spaces. To avoid trailing whitespace, we don't indent empty lines
+# indent4 TEXT_BLOCK
+indent4() {
+    printf '%s' "$1" | awk '{ if ($0 != "") { print "    "$0 } else { print $0 }}'
+}
+
+assertValidEnvName() {
+    case "$1" in
+        *=*) printf '\n%s\n' "#error Illegal environment variable name \`$1\` (cannot contain \`=\`)";;
+        "")  printf '\n%s\n' "#error Environment variable name can't be empty.";;
+    esac
+}
+
+setEnvPrefixFn() {
+    printf '%s' "\
+void set_env_prefix(char *env, char *sep, char *prefix) {
+    char *existing = getenv(env);
+    if (existing) {
+        char *val;
+        assert_success(asprintf(&val, \"%s%s%s\", prefix, sep, existing));
+        assert_success(setenv(env, val, 1));
+        free(val);
+    } else {
+        assert_success(setenv(env, prefix, 1));
+    }
+}
+"
+}
+
+setEnvSuffixFn() {
+    printf '%s' "\
+void set_env_suffix(char *env, char *sep, char *suffix) {
+    char *existing = getenv(env);
+    if (existing) {
+        char *val;
+        assert_success(asprintf(&val, \"%s%s%s\", existing, sep, suffix));
+        assert_success(setenv(env, val, 1));
+        free(val);
+    } else {
+        assert_success(setenv(env, suffix, 1));
+    }
+}
+"
+}
+
+resolveArgv0Fn() {
+  printf '%s' "\
+char *resolve_argv0(char *argv0) {
+  if (strchr(argv0, '/') != NULL) {
+    return argv0;
+  }
+  char *path = getenv(\"PATH\");
+  if (path == NULL) {
+    return argv0;
+  }
+  char *path_copy = strdup(path);
+  if (path_copy == NULL) {
+    return argv0;
+  }
+  char *dir = strtok(path_copy, \":\");
+  while (dir != NULL) {
+    char *candidate = malloc(strlen(dir) + strlen(argv0) + 2);
+    if (candidate == NULL) {
+      free(path_copy);
+      return argv0;
+    }
+    sprintf(candidate, \"%s/%s\", dir, argv0);
+    if (access(candidate, X_OK) == 0) {
+      free(path_copy);
+      return candidate;
+    }
+    free(candidate);
+    dir = strtok(NULL, \":\");
+  }
+  free(path_copy);
+  return argv0;
+}
+"
+}
+
+# Embed a C string which shows up as readable text in the compiled binary wrapper,
+# giving instructions for recreating the wrapper.
+# Keep in sync with makeBinaryWrapper.extractCmd
+docstring() {
+    printf '%s' "const char * DOCSTRING = \"$(escapeStringLiteral "
+
+
+# ------------------------------------------------------------------------------------
+# The C-code for this binary wrapper has been generated using the following command:
+
+
+makeCWrapper $(formatArgs "$@")
+
+
+# (Use \`nix-shell -p makeBinaryWrapper\` to get access to makeCWrapper in your shell)
+# ------------------------------------------------------------------------------------
+
+
+")\";"
+}
+
+# formatArgs EXECUTABLE ARGS
+formatArgs() {
+    printf '%s' "${1@Q}"
+    shift
+    while [ $# -gt 0 ]; do
+        case "$1" in
+            --set)
+                formatArgsLine 2 "$@"
+                shift 2
+            ;;
+            --set-default)
+                formatArgsLine 2 "$@"
+                shift 2
+            ;;
+            --unset)
+                formatArgsLine 1 "$@"
+                shift 1
+            ;;
+            --prefix)
+                formatArgsLine 3 "$@"
+                shift 3
+            ;;
+            --suffix)
+                formatArgsLine 3 "$@"
+                shift 3
+            ;;
+            --chdir)
+                formatArgsLine 1 "$@"
+                shift 1
+            ;;
+            --add-flags)
+                formatArgsLine 1 "$@"
+                shift 1
+            ;;
+            --append-flags)
+                formatArgsLine 1 "$@"
+                shift 1
+            ;;
+            --argv0)
+                formatArgsLine 1 "$@"
+                shift 1
+            ;;
+            --inherit-argv0)
+                formatArgsLine 0 "$@"
+            ;;
+        esac
+        shift
+    done
+    printf '%s\n' ""
+}
+
+# formatArgsLine ARG_COUNT ARGS
+formatArgsLine() {
+    local ARG_COUNT LENGTH
+    ARG_COUNT=$1
+    LENGTH=$#
+    shift
+    printf '%s' $' \\\n    '"$1"
+    shift
+    while [ "$ARG_COUNT" -gt $((LENGTH - $# - 2)) ]; do
+        printf ' %s' "${1@Q}"
+        shift
+    done
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/make-coverage-analysis-report.sh b/nixpkgs/pkgs/build-support/setup-hooks/make-coverage-analysis-report.sh
new file mode 100644
index 000000000000..9108b4c50355
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/make-coverage-analysis-report.sh
@@ -0,0 +1,25 @@
+postPhases+=" coverageReportPhase"
+
+coverageReportPhase() {
+    lcov --directory . --capture --output-file app.info
+    set -o noglob
+    lcov --remove app.info ${lcovFilter:-"/nix/store/*"} > app2.info
+    set +o noglob
+    mv app2.info app.info
+
+    mkdir -p $out/coverage
+    genhtml app.info $lcovExtraTraceFiles -o $out/coverage > log
+
+    # Grab the overall coverage percentage so that Hydra can plot it over time.
+    mkdir -p $out/nix-support
+    lineCoverage="$(sed 's/.*lines\.*: \([0-9\.]\+\)%.*/\1/; t ; d' log)"
+    functionCoverage="$(sed 's/.*functions\.*: \([0-9\.]\+\)%.*/\1/; t ; d' log)"
+    if [ -z "$lineCoverage" -o -z "$functionCoverage" ]; then
+        echo "failed to get coverage statistics"
+        exit 1
+    fi
+    echo "lineCoverage $lineCoverage %" >> $out/nix-support/hydra-metrics
+    echo "functionCoverage $functionCoverage %" >> $out/nix-support/hydra-metrics
+
+    echo "report coverage $out/coverage" >> $out/nix-support/hydra-build-products
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/make-symlinks-relative.sh b/nixpkgs/pkgs/build-support/setup-hooks/make-symlinks-relative.sh
new file mode 100644
index 000000000000..b07b0c5ae804
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/make-symlinks-relative.sh
@@ -0,0 +1,37 @@
+# symlinks are often created in postFixup
+# don't use fixupOutputHooks, it is before postFixup
+postFixupHooks+=(_makeSymlinksRelativeInAllOutputs)
+
+# For every symlink in $output that refers to another file in $output
+# ensure that the symlink is relative. This removes references to the output
+# has from the resulting store paths and thus the NAR files.
+_makeSymlinksRelative() {
+    local symlinkTarget
+
+    if [ "${dontRewriteSymlinks-}" ] || [ ! -e "$prefix" ]; then
+       return
+    fi
+
+    while IFS= read -r -d $'\0' f; do
+        symlinkTarget=$(readlink "$f")
+        if [[ "$symlinkTarget"/ != "$prefix"/* ]]; then
+            # skip this symlink as it doesn't point to $prefix
+            continue
+        fi
+
+        if [ ! -e "$symlinkTarget" ]; then
+            echo "the symlink $f is broken, it points to $symlinkTarget (which is missing)"
+        fi
+
+        echo "rewriting symlink $f to be relative to $prefix"
+        ln -snrf "$symlinkTarget" "$f"
+
+    done < <(find $prefix -type l -print0)
+}
+
+_makeSymlinksRelativeInAllOutputs() {
+  local output
+  for output in $(getAllOutputNames); do
+    prefix="${!output}" _makeSymlinksRelative
+  done
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh b/nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh
new file mode 100644
index 000000000000..cba158bd31ea
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh
@@ -0,0 +1,225 @@
+# Assert that FILE exists and is executable
+#
+# assertExecutable FILE
+assertExecutable() {
+    local file="$1"
+    [[ -f "$file" && -x "$file" ]] || \
+        die "Cannot wrap '$file' because it is not an executable file"
+}
+
+# construct an executable file that wraps the actual executable
+# makeWrapper EXECUTABLE OUT_PATH ARGS
+
+# ARGS:
+# --argv0        NAME    : set the name of the executed process to NAME
+#                          (if unset or empty, defaults to EXECUTABLE)
+# --inherit-argv0        : the executable inherits argv0 from the wrapper.
+#                          (use instead of --argv0 '$0')
+# --resolve-argv0        : if argv0 doesn't include a / character, resolve it against PATH
+# --set          VAR VAL : add VAR with value VAL to the executable's environment
+# --set-default  VAR VAL : like --set, but only adds VAR if not already set in
+#                          the environment
+# --unset        VAR     : remove VAR from the environment
+# --chdir        DIR     : change working directory (use instead of --run "cd DIR")
+# --run          COMMAND : run command before the executable
+# --add-flags    ARGS    : prepend ARGS to the invocation of the executable
+#                          (that is, *before* any arguments passed on the command line)
+# --append-flags ARGS    : append ARGS to the invocation of the executable
+#                          (that is, *after* any arguments passed on the command line)
+
+# --prefix          ENV SEP VAL   : suffix/prefix ENV with VAL, separated by SEP
+# --suffix
+# --prefix-each     ENV SEP VALS  : like --prefix, but VALS is a list
+# --suffix-each     ENV SEP VALS  : like --suffix, but VALS is a list
+# --prefix-contents ENV SEP FILES : like --suffix-each, but contents of FILES
+#                                   are read first and used as VALS
+# --suffix-contents
+makeWrapper() { makeShellWrapper "$@"; }
+makeShellWrapper() {
+    local original="$1"
+    local wrapper="$2"
+    local params varName value command separator n fileNames
+    local argv0 flagsBefore flagsAfter flags
+
+    assertExecutable "$original"
+
+    # Write wrapper code which adds `value` to the beginning or end of
+    # the list variable named by `varName`, depending on the `mode`
+    # specified.
+    #
+    # A value which is already part of the list will not be added
+    # again. If this is the case and the `suffix` mode is used, the
+    # list won't be touched at all. The `prefix` mode will however
+    # move the last matching instance of the value to the beginning
+    # of the list. Any remaining duplicates of the value will be left
+    # as-is.
+    addValue() {
+        local mode="$1"       # `prefix` or `suffix` to add to the beginning or end respectively
+        local varName="$2"    # name of list variable to add to
+        local separator="$3"  # character used to separate elements of list
+        local value="$4"      # one value, or multiple values separated by `separator`, to add to list
+
+        # Disable file globbing, since bash will otherwise try to find
+        # filenames matching the the value to be prefixed/suffixed if
+        # it contains characters considered wildcards, such as `?` and
+        # `*`. We want the value as is, except we also want to split
+        # it on on the separator; hence we can't quote it.
+        local reenableGlob=0
+        if [[ ! -o noglob ]]; then
+            reenableGlob=1
+        fi
+        set -o noglob
+
+        if [[ -n "$value" ]]; then
+            local old_ifs=$IFS
+            IFS=$separator
+
+            if [[ "$mode" == '--prefix'* ]]; then
+                # Keep the order of the components as written when
+                # prefixing; normally, they would be added in the
+                # reverse order.
+                local tmp=
+                for v in $value; do
+                    tmp=$v${tmp:+$separator}$tmp
+                done
+                value="$tmp"
+            fi
+            for v in $value; do
+                {
+                    echo "$varName=\${$varName:+${separator@Q}\$$varName${separator@Q}}"               # add separators on both ends unless empty
+                    if [[ "$mode" == '--prefix'* ]]; then                                              # -- in prefix mode --
+                        echo "$varName=\${$varName/${separator@Q}${v@Q}${separator@Q}/${separator@Q}}" # remove the first instance of the value (if any)
+                        echo "$varName=${v@Q}\$$varName"                                               # prepend the value
+                    elif [[ "$mode" == '--suffix'* ]]; then                                            # -- in suffix mode --
+                        echo "if [[ \$$varName != *${separator@Q}${v@Q}${separator@Q}* ]]; then"       # if the value isn't already in the list
+                        echo "    $varName=\$$varName${v@Q}"                                           # append the value
+                        echo "fi"
+                    else
+                        echo "unknown mode $mode!" 1>&2
+                        exit 1
+                    fi
+                    echo "$varName=\${$varName#${separator@Q}}"                                        # remove leading separator
+                    echo "$varName=\${$varName%${separator@Q}}"                                        # remove trailing separator
+                    echo "export $varName"
+                } >> "$wrapper"
+            done
+            IFS=$old_ifs
+        fi
+
+        if (( reenableGlob )); then
+            set +o noglob
+        fi
+    }
+
+    mkdir -p "$(dirname "$wrapper")"
+
+    echo "#! @shell@ -e" > "$wrapper"
+
+    params=("$@")
+    for ((n = 2; n < ${#params[*]}; n += 1)); do
+        p="${params[$n]}"
+
+        if [[ "$p" == "--set" ]]; then
+            varName="${params[$((n + 1))]}"
+            value="${params[$((n + 2))]}"
+            n=$((n + 2))
+            echo "export $varName=${value@Q}" >> "$wrapper"
+        elif [[ "$p" == "--set-default" ]]; then
+            varName="${params[$((n + 1))]}"
+            value="${params[$((n + 2))]}"
+            n=$((n + 2))
+            echo "export $varName=\${$varName-${value@Q}}" >> "$wrapper"
+        elif [[ "$p" == "--unset" ]]; then
+            varName="${params[$((n + 1))]}"
+            n=$((n + 1))
+            echo "unset $varName" >> "$wrapper"
+        elif [[ "$p" == "--chdir" ]]; then
+            dir="${params[$((n + 1))]}"
+            n=$((n + 1))
+            echo "cd ${dir@Q}" >> "$wrapper"
+        elif [[ "$p" == "--run" ]]; then
+            command="${params[$((n + 1))]}"
+            n=$((n + 1))
+            echo "$command" >> "$wrapper"
+        elif [[ ("$p" == "--suffix") || ("$p" == "--prefix") ]]; then
+            varName="${params[$((n + 1))]}"
+            separator="${params[$((n + 2))]}"
+            value="${params[$((n + 3))]}"
+            n=$((n + 3))
+            addValue "$p" "$varName" "$separator" "$value"
+        elif [[ ("$p" == "--suffix-each") || ("$p" == "--prefix-each") ]]; then
+            varName="${params[$((n + 1))]}"
+            separator="${params[$((n + 2))]}"
+            values="${params[$((n + 3))]}"
+            n=$((n + 3))
+            for value in $values; do
+                addValue "$p" "$varName" "$separator" "$value"
+            done
+        elif [[ ("$p" == "--suffix-contents") || ("$p" == "--prefix-contents") ]]; then
+            varName="${params[$((n + 1))]}"
+            separator="${params[$((n + 2))]}"
+            fileNames="${params[$((n + 3))]}"
+            n=$((n + 3))
+            for fileName in $fileNames; do
+                contents="$(cat "$fileName")"
+                addValue "$p" "$varName" "$separator" "$contents"
+            done
+        elif [[ "$p" == "--add-flags" ]]; then
+            flags="${params[$((n + 1))]}"
+            n=$((n + 1))
+            flagsBefore="${flagsBefore-} $flags"
+        elif [[ "$p" == "--append-flags" ]]; then
+            flags="${params[$((n + 1))]}"
+            n=$((n + 1))
+            flagsAfter="${flagsAfter-} $flags"
+        elif [[ "$p" == "--argv0" ]]; then
+            argv0="${params[$((n + 1))]}"
+            n=$((n + 1))
+        elif [[ "$p" == "--inherit-argv0" ]]; then
+            # Whichever comes last of --argv0 and --inherit-argv0 wins
+            argv0='$0'
+        elif [[ "$p" == "--resolve-argv0" ]]; then
+            # this is noop in shell wrappers, since bash will always resolve $0
+            resolve_argv0=1
+        else
+            die "makeWrapper doesn't understand the arg $p"
+        fi
+    done
+
+    echo exec ${argv0:+-a \"$argv0\"} \""$original"\" \
+         "${flagsBefore-}" '"$@"' "${flagsAfter-}" >> "$wrapper"
+
+    chmod +x "$wrapper"
+}
+
+addSuffix() {
+    suffix="$1"
+    shift
+    for name in "$@"; do
+        echo "$name$suffix"
+    done
+}
+
+filterExisting() {
+    for fn in "$@"; do
+        if test -e "$fn"; then
+            echo "$fn"
+        fi
+    done
+}
+
+# Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...>
+wrapProgram() { wrapProgramShell "$@"; }
+wrapProgramShell() {
+    local prog="$1"
+    local hidden
+
+    assertExecutable "$prog"
+
+    hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped
+    while [ -e "$hidden" ]; do
+      hidden="${hidden}_"
+    done
+    mv "$prog" "$hidden"
+    makeShellWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}"
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/move-build-tree.sh b/nixpkgs/pkgs/build-support/setup-hooks/move-build-tree.sh
new file mode 100644
index 000000000000..2718070f3933
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/move-build-tree.sh
@@ -0,0 +1,12 @@
+prePhases+=" moveBuildDir"
+
+moveBuildDir() {
+    mkdir -p $out/.build
+    cd $out/.build
+}
+
+postPhases+=" removeBuildDir"
+
+removeBuildDir() {
+    rm -rf $out/.build
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/move-docs.sh b/nixpkgs/pkgs/build-support/setup-hooks/move-docs.sh
new file mode 100644
index 000000000000..3f961155d201
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/move-docs.sh
@@ -0,0 +1,27 @@
+# This setup hook moves $out/{man,doc,info} to $out/share.
+
+preFixupHooks+=(_moveToShare)
+
+_moveToShare() {
+    if [ -n "$__structuredAttrs" ]; then
+        if [ -z "${forceShare-}" ]; then
+            forceShare=( man doc info )
+        fi
+    else
+        forceShare=( ${forceShare:-man doc info} )
+    fi
+
+    if [[ -z "$out" ]]; then return; fi
+
+    for d in "${forceShare[@]}"; do
+        if [ -d "$out/$d" ]; then
+            if [ -d "$out/share/$d" ]; then
+                echo "both $d/ and share/$d/ exist!"
+            else
+                echo "moving $out/$d to $out/share/$d"
+                mkdir -p $out/share
+                mv $out/$d $out/share/
+            fi
+        fi
+    done
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/move-lib64.sh b/nixpkgs/pkgs/build-support/setup-hooks/move-lib64.sh
new file mode 100644
index 000000000000..9517af797323
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/move-lib64.sh
@@ -0,0 +1,22 @@
+# This setup hook, for each output, moves everything in $output/lib64
+# to $output/lib, and replaces $output/lib64 with a symlink to
+# $output/lib. The rationale is that lib64 directories are unnecessary
+# in Nix (since 32-bit and 64-bit builds of a package are in different
+# store paths anyway).
+# If the move would overwrite anything, it should fail on rmdir.
+
+fixupOutputHooks+=(_moveLib64)
+
+_moveLib64() {
+    if [ "${dontMoveLib64-}" = 1 ]; then return; fi
+    if [ ! -e "$prefix/lib64" -o -L "$prefix/lib64" ]; then return; fi
+    echo "moving $prefix/lib64/* to $prefix/lib"
+    mkdir -p $prefix/lib
+    shopt -s dotglob
+    for i in $prefix/lib64/*; do
+        mv --no-clobber "$i" $prefix/lib
+    done
+    shopt -u dotglob
+    rmdir $prefix/lib64
+    ln -s lib $prefix/lib64
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/move-sbin.sh b/nixpkgs/pkgs/build-support/setup-hooks/move-sbin.sh
new file mode 100644
index 000000000000..1c0c4dc9f2d9
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/move-sbin.sh
@@ -0,0 +1,19 @@
+# This setup hook, for each output, moves everything in $output/sbin
+# to $output/bin, and replaces $output/sbin with a symlink to
+# $output/bin.
+
+fixupOutputHooks+=(_moveSbin)
+
+_moveSbin() {
+    if [ "${dontMoveSbin-}" = 1 ]; then return; fi
+    if [ ! -e "$prefix/sbin" -o -L "$prefix/sbin" ]; then return; fi
+    echo "moving $prefix/sbin/* to $prefix/bin"
+    mkdir -p $prefix/bin
+    shopt -s dotglob
+    for i in $prefix/sbin/*; do
+        mv "$i" $prefix/bin
+    done
+    shopt -u dotglob
+    rmdir $prefix/sbin
+    ln -s bin $prefix/sbin
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/move-systemd-user-units.sh b/nixpkgs/pkgs/build-support/setup-hooks/move-systemd-user-units.sh
new file mode 100755
index 000000000000..33e89898262f
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/move-systemd-user-units.sh
@@ -0,0 +1,25 @@
+# shellcheck shell=bash
+
+# This setup hook, for each output, moves everything in
+# $output/lib/systemd/user to $output/share/systemd/user, and replaces
+# $output/lib/systemd/user with a symlink to
+# $output/share/systemd/user.
+
+fixupOutputHooks+=(_moveSystemdUserUnits)
+
+_moveSystemdUserUnits() {
+    if [ "${dontMoveSystemdUserUnits:-0}" = 1 ]; then return; fi
+    if [ ! -e "${prefix:?}/lib/systemd/user" ]; then return; fi
+    local source="$prefix/lib/systemd/user"
+    local target="$prefix/share/systemd/user"
+    echo "moving $source/* to $target"
+    mkdir -p "$target"
+    (
+      shopt -s dotglob
+      for i in "$source"/*; do
+          mv "$i" "$target"
+      done
+    )
+    rmdir "$source"
+    ln -s "$target" "$source"
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/mpi-check-hook/default.nix b/nixpkgs/pkgs/build-support/setup-hooks/mpi-check-hook/default.nix
new file mode 100644
index 000000000000..2834cfcc44ff
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/mpi-check-hook/default.nix
@@ -0,0 +1,5 @@
+{ callPackage, makeSetupHook }:
+
+makeSetupHook {
+  name = "mpi-checkPhase-hook";
+} ./mpi-check-hook.sh
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/mpi-check-hook/mpi-check-hook.sh b/nixpkgs/pkgs/build-support/setup-hooks/mpi-check-hook/mpi-check-hook.sh
new file mode 100644
index 000000000000..fca1f7b7f932
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/mpi-check-hook/mpi-check-hook.sh
@@ -0,0 +1,54 @@
+preCheckHooks+=('setupMpiCheck')
+preInstallCheckHooks+=('setupMpiCheck')
+
+
+setupMpiCheck() {
+  # Find out which MPI implementation we are using
+  # and set safe defaults that are guaranteed to run
+  # on any build machine
+
+  mpiType="NONE"
+
+  # OpenMPI signature
+  if command ompi_info &> /dev/null; then
+    mpiType="openmpi"
+  fi
+
+  # MPICH based implementations
+  if command mpichversion &> /dev/null; then
+    if [ "$mpiType" != "NONE" ]; then
+      echo "WARNING: found OpenMPI and MPICH/MVAPICH executables"
+    fi
+
+    version=$(mpichversion)
+    if [[ "$version" == *"MPICH"* ]]; then
+      mpiType="MPICH"
+    fi
+    if [[ "$version" == *"MVAPICH"* ]]; then
+      mpiType="MVAPICH"
+    fi
+  fi
+
+  echo "Found MPI implementation: $mpiType"
+
+  case $mpiType in
+    openmpi)
+      # make sure the test starts even if we have less than the requested amount of cores
+      export OMPI_MCA_rmaps_base_oversubscribe=1
+      # Disable CPU pinning
+      export OMPI_MCA_hwloc_base_binding_policy=none
+      ;;
+    MPICH)
+      # Fix to make mpich run in a sandbox
+      export HYDRA_IFACE=lo
+      ;;
+    MVAPICH)
+      # Disable CPU pinning
+      export MV2_ENABLE_AFFINITY=0
+      ;;
+  esac
+
+  # Limit number of OpenMP threads. Default is "all cores".
+  export OMP_NUM_THREADS=1
+}
+
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/multiple-outputs.sh b/nixpkgs/pkgs/build-support/setup-hooks/multiple-outputs.sh
new file mode 100644
index 000000000000..45096d833b42
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/multiple-outputs.sh
@@ -0,0 +1,214 @@
+# The base package for automatic multiple-output splitting. Used in stdenv as well.
+preConfigureHooks+=(_multioutConfig)
+preFixupHooks+=(_multioutDocs)
+preFixupHooks+=(_multioutDevs)
+postFixupHooks+=(_multioutPropagateDev)
+
+# _assignFirst varName otherVarNames*
+#
+# Set the value of the variable named $varName to the first of otherVarNames
+# that refers to a non-empty variable name.
+#
+# If none of otherVarNames refers to a non-empty variable, the error message is
+# specific to this function's use case, which is setting up the output variables.
+_assignFirst() {
+    local varName="$1"
+    local _var
+    local REMOVE=REMOVE # slightly hacky - we allow REMOVE (i.e. not a variable name)
+    shift
+    for _var in "$@"; do
+        if [ -n "${!_var-}" ]; then eval "${varName}"="${_var}"; return; fi
+    done
+    echo
+    echo "error: _assignFirst: could not find a non-empty variable whose name to assign to ${varName}."
+    echo "       The following variables were all unset or empty:"
+    echo "           $*"
+    if [ -z "${out:-}" ]; then
+        echo '       If you do not want an "out" output in your derivation, make sure to define'
+        echo '       the other specific required outputs. This can be achieved by picking one'
+        echo "       of the above as an output."
+        echo '       You do not have to remove "out" if you want to have a different default'
+        echo '       output, because the first output is taken as a default.'
+        echo
+    fi
+    return 1 # none found
+}
+
+# Same as _assignFirst, but only if "$1" = ""
+_overrideFirst() {
+    if [ -z "${!1-}" ]; then
+        _assignFirst "$@"
+    fi
+}
+
+
+# Setup chains of sane default values with easy overridability.
+# The variables are global to be usable anywhere during the build.
+# Typical usage in package is defining outputBin = "dev";
+
+_overrideFirst outputDev "dev" "out"
+_overrideFirst outputBin "bin" "out"
+
+_overrideFirst outputInclude "$outputDev"
+
+# so-libs are often among the main things to keep, and so go to $out
+_overrideFirst outputLib "lib" "out"
+
+_overrideFirst outputDoc "doc" "out"
+_overrideFirst outputDevdoc "devdoc" REMOVE # documentation for developers
+# man and info pages are small and often useful to distribute with binaries
+_overrideFirst outputMan "man" "$outputBin"
+_overrideFirst outputDevman "devman" "devdoc" "$outputMan"
+_overrideFirst outputInfo "info" "$outputBin"
+
+
+# Add standard flags to put files into the desired outputs.
+_multioutConfig() {
+    if [ "$(getAllOutputNames)" = "out" ] || [ -z "${setOutputFlags-1}" ]; then return; fi;
+
+    # try to detect share/doc/${shareDocName}
+    # Note: sadly, $configureScript detection comes later in configurePhase,
+    #   and reordering would cause more trouble than worth.
+    if [ -z "${shareDocName:-}" ]; then
+        local confScript="${configureScript:-}"
+        if [ -z "$confScript" ] && [ -x ./configure ]; then
+            confScript=./configure
+        fi
+        if [ -f "$confScript" ]; then
+            local shareDocName="$(sed -n "s/^PACKAGE_TARNAME='\(.*\)'$/\1/p" < "$confScript")"
+        fi
+                                    # PACKAGE_TARNAME sometimes contains garbage.
+        if [ -z "$shareDocName" ] || echo "$shareDocName" | grep -q '[^a-zA-Z0-9_-]'; then
+            shareDocName="$(echo "$name" | sed 's/-[^a-zA-Z].*//')"
+        fi
+    fi
+
+    prependToVar configureFlags \
+        --bindir="${!outputBin}"/bin --sbindir="${!outputBin}"/sbin \
+        --includedir="${!outputInclude}"/include --oldincludedir="${!outputInclude}"/include \
+        --mandir="${!outputMan}"/share/man --infodir="${!outputInfo}"/share/info \
+        --docdir="${!outputDoc}"/share/doc/"${shareDocName}" \
+        --libdir="${!outputLib}"/lib --libexecdir="${!outputLib}"/libexec \
+        --localedir="${!outputLib}"/share/locale
+
+    prependToVar installFlags \
+        pkgconfigdir="${!outputDev}"/lib/pkgconfig \
+        m4datadir="${!outputDev}"/share/aclocal aclocaldir="${!outputDev}"/share/aclocal
+}
+
+
+# Add rpath prefixes to library paths, and avoid stdenv doing it for $out.
+_addRpathPrefix "${!outputLib}"
+NIX_NO_SELF_RPATH=1
+
+
+# Move subpaths that match pattern $1 from under any output/ to the $2 output/
+# Beware: only globbing patterns are accepted, e.g.: * ? [abc]
+# A special target "REMOVE" is allowed: moveToOutput foo REMOVE
+moveToOutput() {
+    local patt="$1"
+    local dstOut="$2"
+    local output
+    for output in $(getAllOutputNames); do
+        if [ "${!output}" = "$dstOut" ]; then continue; fi
+        local srcPath
+        for srcPath in "${!output}"/$patt; do
+            # apply to existing files/dirs, *including* broken symlinks
+            if [ ! -e "$srcPath" ] && [ ! -L "$srcPath" ]; then continue; fi
+
+            if [ "$dstOut" = REMOVE ]; then
+                echo "Removing $srcPath"
+                rm -r "$srcPath"
+            else
+                local dstPath="$dstOut${srcPath#${!output}}"
+                echo "Moving $srcPath to $dstPath"
+
+                if [ -d "$dstPath" ] && [ -d "$srcPath" ]
+                then # attempt directory merge
+                    # check the case of trying to move an empty directory
+                    rmdir "$srcPath" --ignore-fail-on-non-empty
+                    if [ -d "$srcPath" ]; then
+                      mv -t "$dstPath" "$srcPath"/*
+                      rmdir "$srcPath"
+                    fi
+                else # usual move
+                    mkdir -p "$(readlink -m "$dstPath/..")"
+                    mv "$srcPath" "$dstPath"
+                fi
+            fi
+
+            # remove empty directories, printing iff at least one gets removed
+            local srcParent="$(readlink -m "$srcPath/..")"
+            if [ -n "$(find "$srcParent" -maxdepth 0 -type d -empty 2>/dev/null)" ]; then
+                echo "Removing empty $srcParent/ and (possibly) its parents"
+                rmdir -p --ignore-fail-on-non-empty "$srcParent" \
+                    2> /dev/null || true # doesn't ignore failure for some reason
+            fi
+        done
+    done
+}
+
+# Move documentation to the desired outputs.
+_multioutDocs() {
+    local REMOVE=REMOVE # slightly hacky - we expand ${!outputFoo}
+
+    moveToOutput share/info "${!outputInfo}"
+    moveToOutput share/doc "${!outputDoc}"
+    moveToOutput share/gtk-doc "${!outputDevdoc}"
+    moveToOutput share/devhelp/books "${!outputDevdoc}"
+
+    # the default outputMan is in $bin
+    moveToOutput share/man "${!outputMan}"
+    moveToOutput share/man/man3 "${!outputDevman}"
+}
+
+# Move development-only stuff to the desired outputs.
+_multioutDevs() {
+    if [ "$(getAllOutputNames)" = "out" ] || [ -z "${moveToDev-1}" ]; then return; fi;
+    moveToOutput include "${!outputInclude}"
+    # these files are sometimes provided even without using the corresponding tool
+    moveToOutput lib/pkgconfig "${!outputDev}"
+    moveToOutput share/pkgconfig "${!outputDev}"
+    moveToOutput lib/cmake "${!outputDev}"
+    moveToOutput share/aclocal "${!outputDev}"
+    # don't move *.la, as libtool needs them in the directory of the library
+
+    for f in "${!outputDev}"/{lib,share}/pkgconfig/*.pc; do
+        echo "Patching '$f' includedir to output ${!outputInclude}"
+        sed -i "/^includedir=/s,=\${prefix},=${!outputInclude}," "$f"
+    done
+}
+
+# Make the "dev" propagate other outputs needed for development.
+_multioutPropagateDev() {
+    if [ "$(getAllOutputNames)" = "out" ]; then return; fi;
+
+    local outputFirst
+    for outputFirst in $(getAllOutputNames); do
+        break
+    done
+    local propagaterOutput="$outputDev"
+    if [ -z "$propagaterOutput" ]; then
+        propagaterOutput="$outputFirst"
+    fi
+
+    # Default value: propagate binaries, includes and libraries
+    if [ -z "${propagatedBuildOutputs+1}" ]; then
+        local po_dirty="$outputBin $outputInclude $outputLib"
+        set +o pipefail
+        propagatedBuildOutputs=`echo "$po_dirty" \
+            | tr -s ' ' '\n' | grep -v -F "$propagaterOutput" \
+            | sort -u | tr '\n' ' ' `
+        set -o pipefail
+    fi
+
+    # The variable was explicitly set to empty or we resolved it so
+    if [ -z "$propagatedBuildOutputs" ]; then
+        return
+    fi
+
+    mkdir -p "${!propagaterOutput}"/nix-support
+    for output in $propagatedBuildOutputs; do
+        echo -n " ${!output}" >> "${!propagaterOutput}"/nix-support/propagated-build-inputs
+    done
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/default.nix b/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/default.nix
new file mode 100644
index 000000000000..854f857020aa
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/default.nix
@@ -0,0 +1,18 @@
+{ lib
+, makeSetupHook
+, which
+, callPackage
+}:
+
+makeSetupHook {
+  name = "patch-ppd-files";
+  substitutions = {
+    which = lib.getBin which;
+    awkscript = ./patch-ppd-lines.awk;
+  };
+  passthru.tests.test = callPackage ./test.nix {};
+  meta = {
+    description = "setup hook to patch executable paths in ppd files";
+    maintainers = [ lib.maintainers.yarny ];
+  };
+} ./patch-ppd-hook.sh
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh b/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh
new file mode 100644
index 000000000000..77322b245b27
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-hook.sh
@@ -0,0 +1,183 @@
+fixupOutputHooks+=(_patchPpdFileCommands4fixupOutputHooks)
+
+
+
+# Install a hook for the `fixupPhase`:
+# If the variable `ppdFileCommands` contains a list of
+# executable names, the hook calls `patchPpdFileCommands`
+# on each output's `/share/cups/model` and `/share/ppds`
+# directories in order to replace calls to those executables.
+
+_patchPpdFileCommands4fixupOutputHooks () {
+    [[ -n $ppdFileCommands ]]  ||  return 0
+    if [[ -d $prefix/share/cups/model ]]; then
+        patchPpdFileCommands "$prefix/share/cups/model" $ppdFileCommands
+    fi
+    if [[ -d $prefix/share/ppds ]]; then
+        patchPpdFileCommands "$prefix/share/ppds" $ppdFileCommands
+    fi
+}
+
+
+
+# patchPpdFileCommands PPD-ROOT PROGNAME...
+#
+# Look for ppd files in the directory PPD-ROOT.
+# Descend into subdirectories, even if they are symlinks.
+# However, ignore ppd files that don't belong to the same
+# prefix ($NIX_STORE/$package_name) as PPD-ROOT-DIR does,
+# to avoid stepping into other package's directories.
+# ppd files may be gzipped; if the are,
+# uncompress them, later recompress them.
+# Skip symlinks to ppd files.
+# PPD-ROOT may also be a single ppd file.
+#
+# Look for the PROGNAME executable in outputs and `buildInputs`,
+# then look for PROGNAME invocations in the ppd files,
+# without path or with common paths like `/usr/bin/$PROGNAME`.
+# Replace those invocations with an absolute path to the
+# corresponding executable from the outputs or `buildInputs`.
+# Executables are searched where CUPS would search them,
+# i.e., in `/bin` and `/lib/cups/filter`.
+#
+# As soon as an executable's path is replaced as
+# described above, the package containing the binary
+# is added to the list of propagated build inputs.
+# This ensures the executable's package is still
+# recognized as runtime dependency of the ppd file
+# even if the ppd file is compressed lateron.
+#
+# PROGNAME may not contain spaces or tabs.
+# The function will also likely fail or produce
+# broken results if PROGNAME contains characters that
+# require shell or regex escaping (e.g. a backslash).
+
+patchPpdFileCommands () {
+
+    local bin binnew binold binoldgrep cupspath path ppdroot ppdrootprefix
+
+    # we will store some temporary data here
+    pushd "$(mktemp -d --tmpdir patch-ppd-file-commands.XXXX)"
+
+    # remember the ppd root path
+    [[ "$1" == $NIX_STORE/* ]]  # ensure it's a store directory
+    ppdroot=$1
+    shift  # now "$@" is the list of binaries
+    ppdrootprefix=${ppdroot%"/${ppdroot#"$NIX_STORE"/*/}"}
+
+    # create `cupspath` (where we should look for binaries),
+    # with these priorities
+    # * outputs of current build before buildInputs
+    # * `/lib/cups/filter' before `/bin`
+    # * add HOST_PATH at end, so we don't miss anything
+    for path in $(getAllOutputNames); do
+        addToSearchPath cupspath "${!path}/lib/cups/filter"
+        addToSearchPath cupspath "${!path}/bin"
+    done
+    for path in ${pkgsHostTarget+"${pkgsHostTarget[@]}"}; do
+        addToSearchPath cupspath "$path/lib/cups/filter"
+        addToSearchPath cupspath "$path/bin"
+    done
+    while read -r -d : path; do
+        addToSearchPath cupspath "$path"
+    done  <<< "${HOST_PATH:+"${HOST_PATH}:"}"
+
+    # create list of compressed ppd files
+    # so we can recompress them later
+    find -L "$ppdroot" -type f -iname '*.ppd.gz' '!' -xtype l -print0  > gzipped
+
+    # decompress gzipped ppd files
+    echo "patchPpdFileCommands: decompressing $(grep -cz '^' < gzipped) gzipped ppd file(s) in $ppdroot"
+    xargs -0r -n 64 -P "$NIX_BUILD_CORES"  gunzip  < gzipped
+
+    # create list of all ppd files to be checked
+    find -L "$ppdroot" -type f -iname '*.ppd' '!' -xtype l -print0  > ppds
+
+    for bin in "$@"; do
+
+        # discover new path
+        binnew=$(PATH=$cupspath '@which@/bin/which' "$bin")
+        echo "patchPpdFileCommands: located binary $binnew"
+
+        # for each binary, we look for the name itself, but
+        # also for a couple of common paths that might be used
+        for binold in {/usr,}/{lib/cups/filter,sbin,bin}/"$bin" "$bin"; do
+
+            # escape regex characters in the old command string
+            binoldgrep=$(sed 's,[]$.*[\^],\\&,g' <<< "$binold")
+            # ...and surround old command with some regex
+            # that singles out shell command invocations
+            # to avoid replacing other strings that might contain the
+            # command name by accident (like "perl" in "perl-script")
+            binoldgrep='\(^\|[;&| '$'\t''"`(]\)'"$binoldgrep"'\($\|[);&| '$'\t''"`<>]\)'
+            # this string is used to *quickly* filter out
+            # unaffected files before the (slower) awk script runs;
+            # note that a similar regex is build in the awk script;
+            # if `binoldgrep` is changed, the awk script should also be checked
+
+            # create list of likely affected files
+            # (might yield exit status != 0 if there are no matches)
+            xargs -0r  grep -lZ "$binoldgrep"  < ppds  > ppds-to-patch  ||  true
+
+            echo "patchPpdFileCommands: $(grep -cz '^' < ppds-to-patch) ppd file(s) contain $binold"
+
+            # actually patch affected ppd files with awk;
+            # this takes some time but can be parallelized;
+            # speed up with LC_ALL=C, https://stackoverflow.com/a/33850386
+            LC_ALL=C xargs -0r -n 64 -P "$NIX_BUILD_CORES"  \
+              awk -i inplace -v old="${binold//\\/\\\\}" -v new="${binnew//\\/\\\\}" -f "@awkscript@"  \
+              < ppds-to-patch
+
+        done
+
+        # create list of affected files
+        xargs -0r  grep -lZF "$binnew"  < ppds  > patched-ppds  ||  true
+
+        echo "patchPpdFileCommands: $(grep -cz '^' < patched-ppds) ppd file(s) patched with $binnew"
+
+        # if the new command is contained in a file,
+        # remember the new path so we can add it to
+        # the list of propagated dependencies later
+        if [[ -s patched-ppds ]]; then
+            printf '%s\0' "${binnew%"/${binnew#"${NIX_STORE}"/*/}"}"  >> dependencies
+        fi
+
+    done
+
+    # recompress ppd files that have been decompressed before
+    echo "patchPpdFileCommands: recompressing $(grep -cz '^' < gzipped) gzipped ppd file(s)"
+    # we can't just hand over the paths of the uncompressed files
+    # to gzip as it would add the lower-cased extension ".gz"
+    # even for files where the original was named ".GZ"
+    xargs -0r -n 1 -P "$NIX_BUILD_CORES"  \
+      "$SHELL" -c 'gzip -9nS ".${0##*.}" "${0%.*}"'  \
+      < gzipped
+
+    # enlist dependencies for propagation;
+    # this is needed in case ppd files are compressed later
+    # (Nix won't find dependency paths in compressed files)
+    if [[ -s dependencies ]]; then
+
+        # weed out duplicates from the dependency list first
+        sort -zu dependencies  > sorted-dependencies
+
+        mkdir -p "$ppdrootprefix/nix-support"
+        while IFS= read -r -d '' path; do
+            printWords "$path" >> "$ppdrootprefix/nix-support/propagated-build-inputs"
+            # stdenv writes it's own `propagated-build-inputs`,
+            # based on the variable `propagatedBuildInputs`,
+            # but only to one output (`outputDev`).
+            # So we also add our dependencies to that variable.
+            # If our file survives as written above, great!
+            # If stdenv overwrits it,
+            # our dependencies will still be added to the file.
+            # The end result might contain too many
+            # propagated dependencies for multi-output packages,
+            # but never a broken package.
+            propagatedBuildInputs+=("$path")
+        done  < sorted-dependencies
+    fi
+
+    popd
+
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-lines.awk b/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-lines.awk
new file mode 100644
index 000000000000..ddb9171fff32
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/patch-ppd-lines.awk
@@ -0,0 +1,50 @@
+BEGIN {
+
+  # ppd file keys are separated from their values by a colon,
+  # but "options" may reside between the key name and the colon;
+  # options are separated from the key by spaces
+  # (we also permit tabs to be on the safe side)
+  FS = "[: \t]";
+
+  # escape regex characters in the old and new command strings
+  gsub(/[]\\.^$(){}|*+?[]/, "\\\\&", old);
+  gsub(/\\/, "\\\\&", new);
+  # ...and surround old command with some regex
+  # that singles out shell command invocations
+  # to avoid replacing other strings that might contain the
+  # command name by accident (like "perl" in "perl-script")
+  new = "\\1" new "\\2";
+  old = "(^|[;&| \\t\"`(])" old "($|[);&| \\t\"`<>])";
+  # note that a similar regex is build in the shell script to
+  # filter out unaffected files before this awk script is called;
+  # if the regex here is changed, the shell script should also be checked
+
+  # list of PPD keys that contain executable names or scripts, see
+  # https://refspecs.linuxfoundation.org/LSB_4.0.0/LSB-Printing/LSB-Printing/ppdext.html
+  # https://www.cups.org/doc/spec-ppd.html
+  cmds["*APAutoSetupTool"] = "";
+  cmds["*APPrinterLowInkTool"] = "";
+  cmds["*FoomaticRIPCommandLine"] = "";
+  cmds["*FoomaticRIPPostPipe"] = "";
+  cmds["*cupsFilter"] = "";
+  cmds["*cupsFilter2"] = "";
+  cmds["*cupsPreFilter"] = "";
+
+}
+
+# since comments always start with "*%",
+# this mechanism also properly recognizes (and ignores) them
+
+{
+
+  # if the current line starts a new key,
+  # check if it is a command-containing key;
+  # also reset the `isCmd` flag if a new file begins
+  if ($0 ~ /^\*/ || FNR == 1)  { isCmd = ($1 in cmds) }
+
+  # replace commands if the current keys might contain commands
+  if (isCmd)  { $0 = gensub(old, new, "g") }
+
+  print
+
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/test.nix b/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/test.nix
new file mode 100644
index 000000000000..4f2996b23510
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/test.nix
@@ -0,0 +1,40 @@
+{ substituteAll
+, diffutils
+, stdenv
+, patchPpdFilesHook
+}:
+
+let
+  input = substituteAll {
+    src = ./test.ppd;
+    keep = "cmp";
+    patch = "cmp";
+    pathkeep = "/bin/cmp";
+    pathpatch = "/bin/cmp";
+  };
+
+  output = substituteAll {
+    src = ./test.ppd;
+    keep = "cmp";
+    patch = "${diffutils}/bin/cmp";
+    pathkeep = "/bin/cmp";
+    pathpatch = "${diffutils}/bin/cmp";
+  };
+in
+
+stdenv.mkDerivation {
+  name = "${patchPpdFilesHook.name}-test";
+  buildInputs = [ diffutils ];
+  nativeBuildInputs = [ diffutils patchPpdFilesHook ];
+  dontUnpack = true;
+  dontInstall = true;
+  ppdFileCommands = [ "cmp" ];
+  preFixup = ''
+    install -D "${input}" "${placeholder "out"}/share/cups/model/test.ppd"
+    install -D "${input}" "${placeholder "out"}/share/ppds/test.ppd"
+  '';
+  postFixup = ''
+    diff --color --report-identical-files "${output}" "${placeholder "out"}/share/cups/model/test.ppd"
+    diff --color --report-identical-files "${output}" "${placeholder "out"}/share/ppds/test.ppd"
+  '';
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/test.ppd b/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/test.ppd
new file mode 100644
index 000000000000..d0ca11ccfe6d
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-ppd-files/test.ppd
@@ -0,0 +1,22 @@
+*% This comment: might look like a command @keep@
+*% but it should be left untouched
+*SomeKey: do not replace this @keep@
+*APAutoSetupTool: do replace this @patch@
+*FoomaticRIPCommandLine: "patch also  @patch@
+in a multi-line command @patch@
+and another line @patch@
+*SomeKey: "stop patching on new non-command key @keep@
+and remember the key in the next line @keep@"
+*cupsFilter option: recognize keys with options @patch@
+*cupsFilter : handle strange spacing;@patch@
+*cupsFilter	: handle tabulator	@patch@
+*cupsFilter: patch common paths @pathpatch@
+*cupsFilter: patch quoted commands "@patch@"
+*cupsFilter: patch commands in subshell (@patch@)
+*cupsFilter: patch commands in subshell `@pathpatch@`
+*cupsFilter: keep uncommon paths /fancy/@pathkeep@
+*cupsFilter: keep entangled commands-@keep@
+*cupsFilter: keep entangled commands\@keep@
+*cupsFilter: keep entangled commands @keep@()
+*cupsFilter: keep entangled commands @pathkeep@-cmd
+*%cupsFilter: This comment should also be left as is @pathkeep@
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/default.nix b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/default.nix
new file mode 100644
index 000000000000..f16644528f00
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/default.nix
@@ -0,0 +1,60 @@
+{ lib
+, callPackage
+, makeSetupHook
+, gnused
+}:
+let
+  tests = import ./test { inherit callPackage; };
+in
+{
+  patchRcPathBash = makeSetupHook
+    {
+      name = "patch-rc-path-bash";
+      meta = with lib; {
+        description = "Setup-hook to inject source-time PATH prefix to a Bash/Ksh/Zsh script";
+        maintainers = with maintainers; [ ShamrockLee ];
+      };
+      passthru.tests = {
+        inherit (tests) test-bash;
+      };
+    } ./patch-rc-path-bash.sh;
+  patchRcPathCsh = makeSetupHook
+    {
+      name = "patch-rc-path-csh";
+      substitutions = {
+        sed = "${gnused}/bin/sed";
+      };
+      meta = with lib; {
+        description = "Setup-hook to inject source-time PATH prefix to a Csh script";
+        maintainers = with maintainers; [ ShamrockLee ];
+      };
+      passthru.tests = {
+        inherit (tests) test-csh;
+      };
+    } ./patch-rc-path-csh.sh;
+  patchRcPathFish = makeSetupHook
+    {
+      name = "patch-rc-path-fish";
+      meta = with lib; {
+        description = "Setup-hook to inject source-time PATH prefix to a Fish script";
+        maintainers = with maintainers; [ ShamrockLee ];
+      };
+      passthru.tests = {
+        inherit (tests) test-fish;
+      };
+    } ./patch-rc-path-fish.sh;
+  patchRcPathPosix = makeSetupHook
+    {
+      name = "patch-rc-path-posix";
+      substitutions = {
+        sed = "${gnused}/bin/sed";
+      };
+      meta = with lib; {
+        description = "Setup-hook to inject source-time PATH prefix to a POSIX shell script";
+        maintainers = with maintainers; [ ShamrockLee ];
+      };
+      passthru.tests = {
+        inherit (tests) test-posix;
+      };
+    } ./patch-rc-path-posix.sh;
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-bash.sh b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-bash.sh
new file mode 100644
index 000000000000..b98b983861b0
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-bash.sh
@@ -0,0 +1,50 @@
+patchRcPathBash(){
+    local FILE_TO_PATCH="$1"
+    local SOURCETIME_PATH="$2"
+    local FILE_TO_WORK_ON="$(mktemp "$(basename "$FILE_TO_PATCH").XXXXXX.tmp")"
+    cat <<EOF >> "$FILE_TO_WORK_ON"
+# Lines to add to PATH the source-time utilities for Nixpkgs packaging
+if [[ -n "\${NIXPKGS_SOURCETIME_PATH-}" ]]; then
+    NIXPKGS_SOURCETIME_PATH_OLD="\$NIXPKGS_SOURCETIME_PATH;\${NIXPKGS_SOURCETIME_PATH_OLD-}"
+fi
+NIXPKGS_SOURCETIME_PATH="$SOURCETIME_PATH"
+if [[ -n "\$PATH" ]]; then
+    PATH="\$NIXPKGS_SOURCETIME_PATH:\$PATH"
+else
+    PATH="\$NIXPKGS_SOURCETIME_PATH"
+fi
+export PATH
+# End of lines to add to PATH source-time utilities for Nixpkgs packaging
+EOF
+    cat "$FILE_TO_PATCH" >> "$FILE_TO_WORK_ON"
+    cat <<EOF >> "$FILE_TO_WORK_ON"
+# Lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
+if [[ -n "\${PATH-}" ]]; then
+    # Remove the inserted section
+    PATH="\${PATH/\$NIXPKGS_SOURCETIME_PATH}"
+    # Remove the duplicated colons
+    PATH="\${PATH//::/:}"
+    # Remove the prefixing colon
+    if [[ -n "\$PATH" && "\${PATH:0:1}" == ":" ]]; then
+        PATH="\${PATH:1}"
+    fi
+    # Remove the trailing colon
+    if [[ -n "\$PATH" && "\${PATH:\${#PATH}-1}" == ":" ]]; then
+        PATH="\${PATH::}"
+    fi
+    export PATH
+fi
+if [[ -n "\${NIXPKGS_SOURCETIME_PATH_OLD-}" ]]; then
+    IFS="" read -r -d ";" NIXPKGS_SOURCETIME_PATH <<< "\$NIXPKGS_SOURCETIME_PATH_OLD"
+    NIXPKGS_SOURCETIME_PATH_OLD="\${NIXPKGS_SOURCETIME_PATH_OLD:\${#NIXPKGS_SOURCETIME_PATH}+1}"
+else
+    unset NIXPKGS_SOURCETIME_PATH
+fi
+if [[ -z "\${NIXPKGS_SOURCETIME_PATH_OLD-}" ]]; then
+    unset NIXPKGS_SOURCETIME_PATH_OLD
+fi
+# End of lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
+EOF
+    cat "$FILE_TO_WORK_ON" > "$FILE_TO_PATCH"
+    rm "$FILE_TO_WORK_ON"
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-csh.sh b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-csh.sh
new file mode 100644
index 000000000000..5e2367003ade
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-csh.sh
@@ -0,0 +1,57 @@
+patchRcPathCsh(){
+    local FILE_TO_PATCH="$1"
+    local SOURCETIME_PATH="$2"
+    local FILE_TO_WORK_ON="$(mktemp "$(basename "$FILE_TO_PATCH").XXXXXX.tmp")"
+    cat <<EOF >> "$FILE_TO_WORK_ON"
+# Lines to add to PATH the source-time utilities for Nixpkgs packaging
+if (\$?NIXPKGS_SOURCETIME_PATH) then
+    if ("\$NIXPKGS_SOURCETIME_PATH" != "") then
+        if (\$?NIXPKGS_SOURCETIME_PATH_OLD) then
+            if ("\$NIXPKGS_SOURCETIME_PATH_OLD" != "")
+                set NIXPKGS_SOURCETIME_PATH_OLD = (\$NIXPKGS_SOURCETIME_PATH \$NIXPKGS_SOURCETIME_PATH_OLD)
+            else
+                set NIXPKGS_SOURCETIME_PATH_OLD = \$NIXPKGS_SOURCETIME_PATH
+            endif
+        else
+            set NIXPKGS_SOURCETIME_PATH_OLD = \$NIXPKGS_SOURCETIME_PATH
+        endif
+    endif
+endif
+set NIXPKGS_SOURCETIME_PATH = "$SOURCETIME_PATH"
+if (! \$?PATH) then
+    setenv PATH ""
+endif
+if ("\$PATH" != "") then
+    setenv PATH "\${NIXPKGS_SOURCETIME_PATH}:\$PATH"
+else
+    setenv PATH "\$NIXPKGS_SOURCETIME_PATH"
+endif
+# End of lines to add to PATH source-time utilities for Nixpkgs packaging
+EOF
+    cat "$FILE_TO_PATCH" >> "$FILE_TO_WORK_ON"
+    cat <<EOF >> "$FILE_TO_WORK_ON"
+# Lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
+if (\$?PATH) then
+    if ("\$PATH" != "") then
+        # Remove the inserted section, the duplicated colons, and the leading and trailing colon
+        setenv PATH \`echo "\$PATH" | @sed@ "s#\${NIXPKGS_SOURCETIME_PATH}##" | @sed@ "s#::#:#g" | @sed@ "s#^:##" | @sed@ 's#:\$##'\`
+    endif
+endif
+if (\$?NIXPKGS_SOURCETIME_PATH_OLD) then
+    if ("\$NIXPKGS_SOURCETIME_PATH_OLD" != "") then
+        set NIXPKGS_SOURCETIME_PATH = \$NIXPKGS_SOURCETIME_PATH_OLD[1]
+        set NIXPKGS_SOURCETIME_PATH_OLD = \$NIXPKGS_SOURCETIME_PATH_OLD[2-]
+    else
+        unset NIXPKGS_SOURCETIME_PATH
+    endif
+    if (NIXPKGS_SOURCETIME_PATH_OLD == "") then
+        unset NIXPKGS_SOURCETIME_PATH_OLD
+    endif
+else
+    unset NIXPKGS_SOURCETIME_PATH
+endif
+# End of lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
+EOF
+    cat "$FILE_TO_WORK_ON" > "$FILE_TO_PATCH"
+    rm "$FILE_TO_WORK_ON"
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-fish.sh b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-fish.sh
new file mode 100644
index 000000000000..3d3e08c57a11
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-fish.sh
@@ -0,0 +1,50 @@
+patchRcPathFish(){
+    local FILE_TO_PATCH="$1"
+    local SOURCETIME_PATH="$2"
+    local FILE_TO_WORK_ON="$(mktemp "$(basename "$FILE_TO_PATCH").XXXXXX.tmp")"
+    cat <<EOF >> "$FILE_TO_WORK_ON"
+# Lines to add to PATH the source-time utilities for Nixpkgs packaging
+if set -q NIXPKGS_SOURCETIME_PATH && test (count \$NIXPKGS_SOURCETIME_PATH) -gt 0
+    set --unpath NIXPKGS_SOURCETIME_PATH_OLD "\$NIXPKGS_SOURCETIME_PATH" \$NIXPKGS_SOURCETIME_PATH_OLD
+end
+set --path NIXPKGS_SOURCETIME_PATH $SOURCETIME_PATH
+set -g --path PATH \$NIXPKGS_SOURCETIME_PATH \$PATH
+# End of lines to add to PATH source-time utilities for Nixpkgs packaging
+EOF
+    cat "$FILE_TO_PATCH" >> "$FILE_TO_WORK_ON"
+    cat <<EOF >> "$FILE_TO_WORK_ON"
+# Lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
+if set -q PATH && test "\$PATH" != "" && test (count \$PATH) -ge (count \$NIXPKGS_SOURCETIME_PATH)
+    # Remove the inserted section
+    for i in (seq 0 (math (count \$PATH) - (count \$NIXPKGS_SOURCETIME_PATH)))
+        for j in (seq 1 (count \$NIXPKGS_SOURCETIME_PATH))
+            if test \$PATH[(math \$i + \$j)] != \$NIXPKGS_SOURCETIME_PATH[\$j]
+                set i -1
+                break
+            end
+        end
+        if test \$i -eq -1
+            continue
+        end
+        if test \$i -eq 0
+            set -g --path PATH \$PATH[(math (count \$NIXPKGS_SOURCETIME_PATH) + 1)..]
+        else
+            set -g --path PATH \$PATH[..\$i] \$PATH[(math (count \$NIXPKGS_SOURCETIME_PATH) + 1 + \$i)..]
+        end
+        break
+    end
+end
+if set -q NIXPKGS_SOURCETIME_PATH_OLD && test (count \$NIXPKGS_SOURCETIME_PATH_OLD) -gt 0
+    set --path NIXPKGS_SOURCETIME_PATH \$NIXPKGS_SOURCETIME_PATH_OLD[1]
+    set --unpath NIXPKGS_SOURCETIME_PATH_OLD \$NIXPKGS_SOURCETIME_PATH_OLD[2..]
+else
+    set -e NIXPKGS_SOURCETIME_PATH
+end
+if set -q NIXPKGS_SOURCETIME_PATH_OLD && test (count \$NIXPKGS_SOURCETIME_PATH_OLD) -eq 0
+    set -e NIXPKGS_SOURCETIME_PATH_OLD
+end
+# End of lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
+EOF
+    cat "$FILE_TO_WORK_ON" > "$FILE_TO_PATCH"
+    rm "$FILE_TO_WORK_ON"
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-posix.sh b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-posix.sh
new file mode 100644
index 000000000000..a3740d4436d9
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/patch-rc-path-posix.sh
@@ -0,0 +1,39 @@
+patchRcPathPosix(){
+    local FILE_TO_PATCH="$1"
+    local SOURCETIME_PATH="$2"
+    local FILE_TO_WORK_ON="$(mktemp "$(basename "$FILE_TO_PATCH").XXXXXX.tmp")"
+    cat <<EOF >> "$FILE_TO_WORK_ON"
+# Lines to add to PATH the source-time utilities for Nixpkgs packaging
+if [ -n "\${NIXPKGS_SOURCETIME_PATH-}" ]; then
+    NIXPKGS_SOURCETIME_PATH_OLD="\$NIXPKGS_SOURCETIME_PATH;\${NIXPKGS_SOURCETIME_PATH_OLD-}"
+fi
+NIXPKGS_SOURCETIME_PATH="$SOURCETIME_PATH"
+if [ -n "\$PATH" ]; then
+    PATH="\$NIXPKGS_SOURCETIME_PATH:\$PATH";
+else
+    PATH="\$NIXPKGS_SOURCETIME_PATH"
+fi
+export PATH
+# End of lines to add to PATH source-time utilities for Nixpkgs packaging
+EOF
+    cat "$FILE_TO_PATCH" >> "$FILE_TO_WORK_ON"
+    cat <<EOF >> "$FILE_TO_WORK_ON"
+# Lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
+if [ -n "\${PATH-}" ]; then
+    PATH="\$(echo "\$PATH" | @sed@ "s#\$NIXPKGS_SOURCETIME_PATH##" | @sed@ "s#::#:#g" | @sed@ "s#^:##" | @sed@ "s#:\\\$##")"
+    export PATH
+fi
+if [ -n "\${NIXPKGS_SOURCETIME_PATH_OLD-}" ]; then
+    NIXPKGS_SOURCETIME_PATH="\$(echo "\$NIXPKGS_SOURCETIME_PATH_OLD" | @sed@ "s#\\([^;]\\);.*#\\1#")"
+    NIXPKGS_SOURCETIME_PATH_OLD="\$(echo "\$NIXPKGS_SOURCETIME_PATH_OLD" | @sed@ "s#[^;];\\(.*\\)#\\1#")"
+else
+    unset NIXPKGS_SOURCETIME_PATH
+fi
+if [ -z "\${NIXPKGS_SOURCETIME_PATH_OLD-}" ]; then
+    unset NIXPKGS_SOURCETIME_PATH_OLD
+fi
+# End of lines to clean up inside PATH the source-time utilities for Nixpkgs packaging
+EOF
+    cat "$FILE_TO_WORK_ON" > "$FILE_TO_PATCH"
+    rm "$FILE_TO_WORK_ON"
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/default.nix b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/default.nix
new file mode 100644
index 000000000000..82bc160387ee
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/default.nix
@@ -0,0 +1,442 @@
+{ callPackage }:
+
+{
+  test-bash = callPackage
+    (
+      { lib
+      , runCommandLocal
+      , bash
+      , hello
+      , ksh
+      , patchRcPathBash
+      , shellcheck
+      , zsh
+      }:
+      runCommandLocal "patch-rc-path-bash-test"
+        {
+          nativeBuildInputs = [
+            bash
+            ksh
+            patchRcPathBash
+            shellcheck
+            zsh
+          ];
+          meta = {
+            description = "Package test of patchActivateBash";
+            inherit (patchRcPathBash.meta) maintainers;
+          };
+        }
+        ''
+          set -eu -o pipefail
+
+
+          # Check the setup hook script
+
+          echo "Running shellcheck against ${./test-sourcing-bash}"
+          shellcheck -s bash --exclude SC1090 ${./test-sourcing-bash}
+          shellcheck -s ksh --exclude SC1090 ${./test-sourcing-bash}
+
+
+          # Test patching a blank file
+
+          echo > blank.bash
+
+          echo "Generating blank_patched.bash from blank.bash"
+          cp blank.bash blank_patched.bash
+          patchRcPathBash blank_patched.bash "$PWD/delta:$PWD/foxtrot"
+
+          echo "Running shellcheck against blank_patched.bash"
+          shellcheck -s bash blank_patched.bash
+          shellcheck -s ksh blank_patched.bash
+
+          echo "Testing in Bash if blank.bash and blank_patched.bash modifies PATH the same way"
+          bash ${./test-sourcing-bash} ./blank.bash ./blank_patched.bash
+
+          echo "Testing in Ksh if blank.bash and blank_patched.bash modifies PATH the same way"
+          ksh ${./test-sourcing-bash} "$PWD/blank.bash" "$PWD/blank_patched.bash"
+
+          echo "Testing in Zsh if blank.bash and blank_patched.bash modifies PATH the same way"
+          zsh ${./test-sourcing-bash} ./blank.bash ./blank_patched.bash
+
+
+          # Test patching silent_hello
+
+          echo "hello > /dev/null" > silent_hello.bash
+
+          echo "Generating silent_hello_patched.bash from silent_hello.bash"
+          cp silent_hello.bash silent_hello_patched.bash
+          patchRcPathBash silent_hello_patched.bash "${hello}/bin"
+
+          echo "Running shellcheck against silent_hello_patched.bash"
+          shellcheck -s bash silent_hello_patched.bash
+
+          echo "Testing in Bash if silent_hello_patched.bash get sourced without error"
+          bash -eu -o pipefail -c ". ./silent_hello_patched.bash"
+
+          echo "Testing in Ksh if silent_hello_patched.bash get sourced without error"
+          ksh -eu -o pipefail -c ". ./silent_hello_patched.bash"
+
+          echo "Testing in Zsh if silent_hello_patched.bash get sourced without error"
+          zsh -eu -o pipefail -c ". ./silent_hello_patched.bash"
+
+
+          # Check the sample source
+
+          echo "Running shellcheck against sample_source.bash"
+          shellcheck -s bash ${./sample_source.bash}
+          shellcheck -s ksh ${./sample_source.bash}
+
+
+          # Test patching the sample source
+
+          cp ${./sample_source.bash} sample_source_patched.bash
+          chmod u+w sample_source_patched.bash
+
+          echo "Generating sample_source_patched.bash from ./sample_source.bash"
+          patchRcPathBash sample_source_patched.bash "$PWD/delta:$PWD/foxtrot"
+
+          echo "Running shellcheck against sample_source_patched.bash"
+          shellcheck -s bash sample_source_patched.bash
+
+          echo "Testing in Bash if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
+          bash ${./test-sourcing-bash} ${./sample_source.bash} ./sample_source_patched.bash
+
+          echo "Testing in Ksh if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
+          ksh ${./test-sourcing-bash} ${./sample_source.bash} "$PWD/sample_source_patched.bash"
+
+          echo "Testing in Zsh if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
+          zsh ${./test-sourcing-bash} ${./sample_source.bash} ./sample_source_patched.bash
+
+
+          # Test double-patching the sample source
+
+          echo "Patching again sample_source_patched.bash"
+          patchRcPathBash sample_source_patched.bash "$PWD/foxtrot:$PWD/golf"
+
+          echo "Running shellcheck against sample_source_patched.bash"
+          shellcheck -s bash sample_source_patched.bash
+          shellcheck -s ksh sample_source_patched.bash
+
+          echo "Testing in Bash if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
+          bash ${./test-sourcing-bash} ${./sample_source.bash} ./sample_source_patched.bash
+
+          echo "Testing in Ksh if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
+          ksh ${./test-sourcing-bash} ${./sample_source.bash} "$PWD/sample_source_patched.bash"
+
+          echo "Testing in Zsh if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
+          zsh ${./test-sourcing-bash} ${./sample_source.bash} ./sample_source_patched.bash
+
+
+          # Create a dummy output
+          touch "$out"
+        ''
+    )
+    { };
+
+
+
+  test-csh = callPackage
+    (
+      { lib
+      , runCommandLocal
+      , gnused
+      , hello
+      , patchRcPathCsh
+      , tcsh
+      }:
+      runCommandLocal "patch-rc-path-csh-test"
+        {
+          nativeBuildInputs = [
+            patchRcPathCsh
+            tcsh
+          ];
+          meta = {
+            description = "Package test of patchActivateCsh";
+            inherit (patchRcPathCsh.meta) maintainers;
+          };
+        }
+        ''
+          set -eu -o pipefail
+
+
+          # Test patching a blank file
+
+          echo > blank.csh
+
+          echo "Generating blank_patched.csh from blank.csh"
+          cp blank.csh blank_patched.csh
+          patchRcPathCsh blank_patched.csh "$PWD/delta:$PWD/foxtrot"
+
+          echo "Testing in Csh if blank.csh and blank_patched.csh modifies PATH the same way"
+          tcsh -e ${./test-sourcing-csh} blank.csh blank_patched.csh
+
+
+          # Test patching silent_hello file
+
+          echo "hello > /dev/null" > silent_hello.csh
+
+          echo "Generating silent_hello_patched.csh from silent_hello.csh"
+          cp silent_hello.csh silent_hello_patched.csh
+          patchRcPathCsh silent_hello_patched.csh "${hello}/bin"
+
+          echo "Testing in Csh if silent_hello_patched.csh get sourced without errer"
+          tcsh -e -c "source silent_hello_patched.csh"
+
+
+          # Generate the sample source
+
+          substitute ${./sample_source.csh.in} sample_source.csh --replace @sed@ ${gnused}/bin/sed
+          chmod u+rw sample_source.csh
+
+
+          # Test patching the sample source
+
+          echo "Generating sample_source_patched.csh from sample_source.csh"
+          cp sample_source.csh sample_source_patched.csh
+          chmod u+w sample_source_patched.csh
+          patchRcPathCsh sample_source_patched.csh "$PWD/delta:$PWD/foxtrot"
+
+          echo "Testing in Csh if sample_source.csh and sample_source_patched.csh modifies PATH the same way"
+          tcsh -e ${./test-sourcing-csh} sample_source.csh sample_source_patched.csh
+
+
+          # Test double-patching the sample source
+
+          echo "Patching again sample_source_patched.csh from sample_source.csh"
+          patchRcPathCsh sample_source_patched.csh "$PWD/foxtrot:$PWD/golf"
+
+          echo "Testing in Csh if sample_source.csh and sample_source_patched.csh modifies PATH the same way"
+          tcsh -e ${./test-sourcing-csh} sample_source.csh sample_source_patched.csh
+
+
+          # Create a dummy output
+          touch "$out"
+        ''
+    )
+    { };
+
+
+
+  test-fish = callPackage
+    (
+      { lib
+      , runCommandLocal
+      , fish
+      , hello
+      , patchRcPathFish
+      }:
+      runCommandLocal "patch-rc-path-fish-test"
+        {
+          nativeBuildInputs = [
+            fish
+            patchRcPathFish
+          ];
+          meta = {
+            description = "Package test of patchActivateFish";
+            inherit (patchRcPathFish.meta) maintainers;
+          };
+        }
+        ''
+          set -eu -o pipefail
+
+
+          # Test patching a blank file
+
+          echo > blank.fish
+
+          echo "Generating blank_patched.fish from blank.fish"
+          cp blank.fish blank_patched.fish
+          patchRcPathFish blank_patched.fish "$PWD/delta:$PWD/foxtrot"
+
+          echo "Testing in Fish if blank.fish and blank_patched.fish modifies PATH the same way"
+          HOME_TEMP="$(mktemp -d temporary_home_XXXXXX)"
+          HOME="$HOME_TEMP" fish ${./test-sourcing-fish} blank.fish blank_patched.fish
+          rm -r "$HOME_TEMP"
+
+
+          # Test patching silent_hello file
+
+          echo "hello > /dev/null" > silent_hello.fish
+
+          echo "Generating silent_hello_patched.fish from silent_hello.fish"
+          cp silent_hello.fish silent_hello_patched.fish
+          patchRcPathFish silent_hello_patched.fish "${hello}/bin"
+
+          echo "Testing in Fish if silent_hello_patched.fish get sourced without error"
+          HOME_TEMP="$(mktemp -d temporary_home_XXXXXX)"
+          HOME="$HOME_TEMP" fish -c "source silent_hello_patched.fish"
+          rm -r "$HOME_TEMP"
+
+
+          # Test patching the sample source
+
+          cp ${./sample_source.fish} sample_source_patched.fish
+          chmod u+w sample_source_patched.fish
+
+          echo "Generating sample_source_patched.fish from ${./sample_source.fish}"
+          patchRcPathFish sample_source_patched.fish "$PWD/delta:$PWD/foxtrot"
+          echo "Testing in Fish if sample_source.fish and sample_source_patched.fish modifies PATH the same way"
+          HOME_TEMP="$(mktemp -d temporary_home_XXXXXX)"
+          HOME="$HOME_TEMP" fish ${./test-sourcing-fish} ${./sample_source.fish} sample_source_patched.fish
+          rm -r "$HOME_TEMP"
+
+
+          # Test double-patching the sample source
+
+          echo "Patching again sample_source_patched.fish from ${./sample_source.fish}"
+          patchRcPathFish sample_source_patched.fish "$PWD/foxtrot:$PWD/golf"
+
+          echo "Testing in Fish if sample_source.fish and sample_source_patched.fish modifies PATH the same way"
+          HOME_TEMP="$(mktemp -d temporary_home_XXXXXX)"
+          HOME="$HOME_TEMP" fish ${./test-sourcing-fish} ${./sample_source.fish} sample_source_patched.fish
+          rm -r "$HOME_TEMP"
+
+
+          # Create a dummy output
+          touch "$out"
+        ''
+    )
+    { };
+
+
+
+  test-posix = callPackage
+    (
+      { lib
+      , runCommandLocal
+      , bash
+      , dash
+      , gnused
+      , hello
+      , ksh
+      , patchRcPathPosix
+      , shellcheck
+      }:
+      runCommandLocal "patch-rc-path-posix-test"
+        {
+          nativeBuildInputs = [
+            bash
+            dash
+            ksh
+            patchRcPathPosix
+            shellcheck
+          ];
+          meta = {
+            description = "Package test of patchActivatePosix";
+            inherit (patchRcPathPosix.meta) maintainers;
+          };
+        }
+        ''
+          set -eu -o pipefail
+
+
+          # Check the setup hook script
+
+          echo "Running shellcheck against ${./test-sourcing-posix}"
+          shellcheck -s sh --exclude SC1090 ${./test-sourcing-posix}
+          shellcheck -s dash --exclude SC1090 ${./test-sourcing-posix}
+
+
+          # Test patching a blank file
+
+          echo > blank.sh
+
+          echo "Generating blank_patched.sh from blank.sh"
+          cp blank.sh blank_patched.sh
+          patchRcPathPosix blank_patched.sh "$PWD/delta:$PWD/foxtrot"
+
+          echo "Running shellcheck against blank_patched.sh"
+          shellcheck -s sh blank_patched.sh
+          shellcheck -s dash blank_patched.sh
+
+          echo "Testing in Bash if blank.sh and blank_patched.sh modifies PATH the same way"
+          bash --posix ${./test-sourcing-posix} ./blank.sh ./blank_patched.sh
+
+          echo "Testing in Dash if blank.sh and blank_patched.sh modifies PATH the same way"
+          dash ${./test-sourcing-posix} ./blank.sh ./blank_patched.sh
+
+          echo "Testing in Ksh if ./blank.sh and ./blank_patched.sh modifies PATH the same way"
+          ksh ${./test-sourcing-posix} "$PWD/blank.sh" "$PWD/blank_patched.sh"
+
+
+          # Test patching silent_hello file
+
+          echo "hello > /dev/null" > silent_hello.sh
+
+          echo "Generating silent_hello_patched.sh from silent_hello.sh"
+          cp silent_hello.sh silent_hello_patched.sh
+          patchRcPathPosix silent_hello_patched.sh "${hello}/bin"
+
+          echo "Running shellcheck against silent_hello_patched.sh"
+          shellcheck -s sh silent_hello_patched.sh
+          shellcheck -s dash silent_hello_patched.sh
+
+          echo "Testing in Bash if silent_hello_patched.sh get sourced without error"
+          bash --posix -eu -c ". ./silent_hello_patched.sh"
+
+          echo "Testing in Dash if silent_hello_patched.sh get sourced without error"
+          dash -eu -c ". ./silent_hello_patched.sh"
+
+          echo "Testing in Ksh if silent_hello_patched.sh get sourced without error"
+          ksh -eu -c ". $PWD/silent_hello_patched.sh"
+
+
+          # Generate the sample source "$PWD/delta:$PWD/foxtrot" "$PWD/delta:$PWD/foxtrot"
+
+          substitute ${./sample_source.sh.in} sample_source.sh --replace @sed@ ${gnused}/bin/sed
+          chmod u+rw sample_source.sh
+
+
+          # Check the sample source
+
+          echo "Running shellcheck against sample_source.sh"
+          shellcheck -s sh sample_source.sh
+          shellcheck -s dash sample_source.sh
+
+
+          # Test patching the sample source
+
+          echo "Generating sample_source_patched.sh from sample_source.sh"
+          cp sample_source.sh sample_source_patched.sh
+          chmod u+w sample_source_patched.sh
+          patchRcPathPosix sample_source_patched.sh "$PWD/delta:$PWD/foxtrot"
+
+          echo "Running shellcheck against sample_source_patched.sh"
+          shellcheck -s sh sample_source_patched.sh
+          shellcheck -s dash sample_source_patched.sh
+
+          echo "Testing in Bash if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
+          bash --posix ${./test-sourcing-posix} "./sample_source.sh" "./sample_source_patched.sh"
+
+          echo "Testing in Dash if sample_source.sh and sample_source_patched.sh modifies PATH the same way"
+          dash ${./test-sourcing-posix} "./sample_source.sh" "./sample_source_patched.sh"
+
+          echo "Testing in Ksh if sample_source.sh and sample_source_patched.sh modifies PATH the same way"
+          ksh ${./test-sourcing-posix} "$PWD/sample_source.sh" "$PWD/sample_source_patched.sh"
+
+
+          # Test double-patching the sample source
+
+          echo "Patching again sample_source_patched.sh"
+          patchRcPathPosix sample_source_patched.sh "$PWD/foxtrot:$PWD/golf"
+
+          echo "Running shellcheck against sample_source_patched.sh"
+          shellcheck -s sh sample_source_patched.sh
+          shellcheck -s dash sample_source_patched.sh
+
+          echo "Testing in Bash if sample_source.bash and sample_source_patched.bash modifies PATH the same way"
+          bash --posix ${./test-sourcing-posix} "./sample_source.sh" "./sample_source_patched.sh"
+
+          echo "Testing in Dash if sample_source.sh and sample_source_patched.sh modifies PATH the same way"
+          dash ${./test-sourcing-posix} "./sample_source.sh" "./sample_source_patched.sh"
+
+          echo "Testing in Ksh if sample_source.sh and sample_source_patched.sh modifies PATH the same way"
+          ksh ${./test-sourcing-posix} "$PWD/sample_source.sh" "$PWD/sample_source_patched.sh"
+
+
+          # Create a dummy output
+          touch "$out"
+        ''
+    )
+    { };
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.bash b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.bash
new file mode 100644
index 000000000000..6cb043e4e70c
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.bash
@@ -0,0 +1,2 @@
+PATH="$PWD/charlie:${PATH/:$PWD\/bravo}"
+export PATH
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.csh.in b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.csh.in
new file mode 100644
index 000000000000..9606458c037e
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.csh.in
@@ -0,0 +1 @@
+setenv PATH $PWD/charlie:`echo "$PATH" | @sed@ "s#:$PWD/bravo##"`
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.fish b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.fish
new file mode 100644
index 000000000000..f638fe5e24d1
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.fish
@@ -0,0 +1,9 @@
+begin
+    for p in $PATH
+        if test $p != "$PWD/bravo"
+            set TEMPORARY_PATH $TEMPORARY_PATH $p
+        end
+    end
+    set -g PATH $TEMPORARY_PATH
+end
+set PATH "$PWD/charlie" $PATH
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.sh.in b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.sh.in
new file mode 100644
index 000000000000..42e64a1ffc08
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/sample_source.sh.in
@@ -0,0 +1,2 @@
+PATH="$PWD/charlie:$(echo "$PATH" | @sed@ "s#:$PWD/bravo##")"
+export PATH
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-bash b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-bash
new file mode 100644
index 000000000000..1b6cc54d8f93
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-bash
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+set -eu -o pipefail
+
+UNPATCHED_SOURCE_FILE="$1"
+PATCHED_SOURCE_FILE="$2"
+ORIG_PATH="$PWD/alfa:$PWD/bravo"
+RESULT_PATH_FROM_UNPATCHED="$(
+    PATH="$ORIG_PATH"; export PATH
+    . "$UNPATCHED_SOURCE_FILE"
+    echo "$PATH"
+)"
+RESULT_PATH_FROM_PATCHED="$(
+    PATH="$ORIG_PATH"; export PATH
+    . "$PATCHED_SOURCE_FILE"
+    echo "$PATH"
+)"
+if [[ "$RESULT_PATH_FROM_UNPATCHED" != "$RESULT_PATH_FROM_PATCHED" ]]; then
+    echo "Result path mismatched: $UNPATCHED_SOURCE_FILE ($RESULT_PATH_FROM_UNPATCHED) and $PATCHED_SOURCE_FILE ($RESULT_PATH_FROM_PATCHED)" >&2
+    exit 1
+fi
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-csh b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-csh
new file mode 100644
index 000000000000..7ddb2ddc1bdc
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-csh
@@ -0,0 +1,13 @@
+#/usr/bin/env tcsh
+
+set UNPATCHED_SOURCE_FILE = "$1"
+set PATCHED_SOURCE_FILE = "$2"
+set ORIG_PATH = "${PWD}/alfa:${PWD}/bravo"
+
+set RESULT_PATH_FROM_UNPATCHED = `setenv PATH "$ORIG_PATH"; source $UNPATCHED_SOURCE_FILE; echo $PATH`
+set RESULT_PATH_FROM_PATCHED = `setenv PATH "$ORIG_PATH"; source $PATCHED_SOURCE_FILE; echo $PATH`
+
+if ($RESULT_PATH_FROM_UNPATCHED != $RESULT_PATH_FROM_PATCHED) then
+    echo "Result path mismatched: $UNPATCHED_SOURCE_FILE ($RESULT_PATH_FROM_UNPATCHED) and $PATCHED_SOURCE_FILE ($RESULT_PATH_FROM_PATCHED)" > /dev/stderr
+    exit 1
+endif
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-fish b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-fish
new file mode 100644
index 000000000000..fcce014331e5
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-fish
@@ -0,0 +1,13 @@
+#/usr/bin/env fish
+
+set UNPATCHED_SOURCE_FILE $argv[1]
+set PATCHED_SOURCE_FILE $argv[2]
+set ORIG_PATH "$PWD/alfa:$PWD/bravo"
+
+set RESULT_PATH_FROM_UNPATCHED (fish -c "set -g PATH \"$ORIG_PATH\"; source $UNPATCHED_SOURCE_FILE; echo \"\$PATH\"")
+set RESULT_PATH_FROM_PATCHED (fish -c "set -g PATH \"$ORIG_PATH\"; source $PATCHED_SOURCE_FILE; echo \"\$PATH\"")
+
+if test "$RESULT_PATH_FROM_UNPATCHED" != "$RESULT_PATH_FROM_PATCHED"
+    echo "Result path mismatched: $UNPATCHED_SOURCE_FILE ($RESULT_PATH_FROM_UNPATCHED) and $PATCHED_SOURCE_FILE ($RESULT_PATH_FROM_PATCHED)" >&2
+    exit 1
+end
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-posix b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-posix
new file mode 100644
index 000000000000..6039b4dcf097
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-rc-path-hooks/test/test-sourcing-posix
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -eu
+
+UNPATCHED_SOURCE_FILE="$1"
+PATCHED_SOURCE_FILE="$2"
+ORIG_PATH="$PWD/alfa:$PWD/bravo"
+RESULT_PATH_FROM_UNPATCHED="$(
+    PATH="$ORIG_PATH"; export PATH
+    . "$UNPATCHED_SOURCE_FILE"
+    echo "$PATH"
+)"
+RESULT_PATH_FROM_PATCHED="$(
+    PATH="$ORIG_PATH"; export PATH
+    . "$PATCHED_SOURCE_FILE"
+    echo "$PATH"
+)"
+if [ "$RESULT_PATH_FROM_UNPATCHED" != "$RESULT_PATH_FROM_PATCHED" ]; then
+    echo "Result path mismatched: $UNPATCHED_SOURCE_FILE ($RESULT_PATH_FROM_UNPATCHED) and $PATCHED_SOURCE_FILE ($RESULT_PATH_FROM_PATCHED)" > /dev/stderr
+    exit 1
+fi
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/patch-shebangs.sh b/nixpkgs/pkgs/build-support/setup-hooks/patch-shebangs.sh
new file mode 100644
index 000000000000..80a29d727c85
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/patch-shebangs.sh
@@ -0,0 +1,148 @@
+# This setup hook causes the fixup phase to rewrite all script
+# interpreter file names (`#!  /path') to paths found in $PATH.  E.g.,
+# /bin/sh will be rewritten to /nix/store/<hash>-some-bash/bin/sh.
+# /usr/bin/env gets special treatment so that ".../bin/env python" is
+# rewritten to /nix/store/<hash>/bin/python.  Interpreters that are
+# already in the store are left untouched.
+# A script file must be marked as executable, otherwise it will not be
+# considered.
+
+fixupOutputHooks+=(patchShebangsAuto)
+
+# Run patch shebangs on a directory or file.
+# Can take multiple paths as arguments.
+# patchShebangs [--build | --host | --update] [--] PATH...
+
+# Flags:
+# --build : Lookup commands available at build-time
+# --host  : Lookup commands available at runtime
+# --update : Update shebang paths that are in Nix store
+
+# Example use cases,
+# $ patchShebangs --host /nix/store/...-hello-1.0/bin
+# $ patchShebangs --build configure
+
+patchShebangs() {
+    local pathName
+    local update
+
+    while [[ $# -gt 0 ]]; do
+        case "$1" in
+        --host)
+            pathName=HOST_PATH
+            shift
+            ;;
+        --build)
+            pathName=PATH
+            shift
+            ;;
+        --update)
+            update=true
+            shift
+            ;;
+        --)
+            shift
+            break
+            ;;
+        -*|--*)
+            echo "Unknown option $1 supplied to patchShebangs" >&2
+            return 1
+            ;;
+        *)
+            break
+            ;;
+        esac
+    done
+
+    echo "patching script interpreter paths in $@"
+    local f
+    local oldPath
+    local newPath
+    local arg0
+    local args
+    local oldInterpreterLine
+    local newInterpreterLine
+
+    if [[ $# -eq 0 ]]; then
+        echo "No arguments supplied to patchShebangs" >&2
+        return 0
+    fi
+
+    local f
+    while IFS= read -r -d $'\0' f; do
+        isScript "$f" || continue
+
+        # read exits unclean if the shebang does not end with a newline, but still assigns the variable.
+        # So if read returns errno != 0, we check if the assigned variable is non-empty and continue.
+        read -r oldInterpreterLine < "$f" || [ "$oldInterpreterLine" ]
+
+        read -r oldPath arg0 args <<< "${oldInterpreterLine:2}"
+
+        if [[ -z "${pathName:-}" ]]; then
+            if [[ -n $strictDeps && $f == "$NIX_STORE"* ]]; then
+                pathName=HOST_PATH
+            else
+                pathName=PATH
+            fi
+        fi
+
+        if [[ "$oldPath" == *"/bin/env" ]]; then
+            if [[ $arg0 == "-S" ]]; then
+                arg0=${args%% *}
+                args=${args#* }
+                newPath="$(PATH="${!pathName}" command -v "env" || true)"
+                args="-S $(PATH="${!pathName}" command -v "$arg0" || true) $args"
+
+            # Check for unsupported 'env' functionality:
+            # - options: something starting with a '-' besides '-S'
+            # - environment variables: foo=bar
+            elif [[ $arg0 == "-"* || $arg0 == *"="* ]]; then
+                echo "$f: unsupported interpreter directive \"$oldInterpreterLine\" (set dontPatchShebangs=1 and handle shebang patching yourself)" >&2
+                exit 1
+            else
+                newPath="$(PATH="${!pathName}" command -v "$arg0" || true)"
+            fi
+        else
+            if [[ -z $oldPath ]]; then
+                # If no interpreter is specified linux will use /bin/sh. Set
+                # oldpath="/bin/sh" so that we get /nix/store/.../sh.
+                oldPath="/bin/sh"
+            fi
+
+            newPath="$(PATH="${!pathName}" command -v "$(basename "$oldPath")" || true)"
+
+            args="$arg0 $args"
+        fi
+
+        # Strip trailing whitespace introduced when no arguments are present
+        newInterpreterLine="$newPath $args"
+        newInterpreterLine=${newInterpreterLine%${newInterpreterLine##*[![:space:]]}}
+
+        if [[ -n "$oldPath" && ( "$update" == true || "${oldPath:0:${#NIX_STORE}}" != "$NIX_STORE" ) ]]; then
+            if [[ -n "$newPath" && "$newPath" != "$oldPath" ]]; then
+                echo "$f: interpreter directive changed from \"$oldInterpreterLine\" to \"$newInterpreterLine\""
+                # escape the escape chars so that sed doesn't interpret them
+                escapedInterpreterLine=${newInterpreterLine//\\/\\\\}
+
+                # Preserve times, see: https://github.com/NixOS/nixpkgs/pull/33281
+                timestamp=$(stat --printf "%y" "$f")
+                sed -i -e "1 s|.*|#\!$escapedInterpreterLine|" "$f"
+                touch --date "$timestamp" "$f"
+            fi
+        fi
+    done < <(find "$@" -type f -perm -0100 -print0)
+}
+
+patchShebangsAuto () {
+    if [[ -z "${dontPatchShebangs-}" && -e "$prefix" ]]; then
+
+        # Dev output will end up being run on the build platform. An
+        # example case of this is sdl2-config. Otherwise, we can just
+        # use the runtime path (--host).
+        if [[ "$output" != out && "$output" = "$outputDev" ]]; then
+            patchShebangs --build "$prefix"
+        else
+            patchShebangs --host "$prefix"
+        fi
+    fi
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/default.nix b/nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/default.nix
new file mode 100644
index 000000000000..e9e77b0bbe6f
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/default.nix
@@ -0,0 +1,8 @@
+{ callPackage, makeSetupHook }:
+
+makeSetupHook {
+  name = "postgresql-test-hook";
+  passthru.tests = {
+    simple = callPackage ./test.nix { };
+  };
+} ./postgresql-test-hook.sh
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/postgresql-test-hook.sh b/nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/postgresql-test-hook.sh
new file mode 100644
index 000000000000..d09153b2d644
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/postgresql-test-hook.sh
@@ -0,0 +1,83 @@
+preCheckHooks+=('postgresqlStart')
+postCheckHooks+=('postgresqlStop')
+
+
+postgresqlStart() {
+
+  # Add default environment variable values
+  #
+  # Client variables:
+  #  - https://www.postgresql.org/docs/current/libpq-envars.html
+  #
+  # Server variables:
+  #  - only PGDATA: https://www.postgresql.org/docs/current/creating-cluster.html
+
+  if [[ "${PGDATA:-}" == "" ]]; then
+    PGDATA="$NIX_BUILD_TOP/postgresql"
+  fi
+  export PGDATA
+
+  if [[ "${PGHOST:-}" == "" ]]; then
+    mkdir -p "$NIX_BUILD_TOP/run/postgresql"
+    PGHOST="$NIX_BUILD_TOP/run/postgresql"
+  fi
+  export PGHOST
+
+  if [[ "${PGUSER:-}" == "" ]]; then
+    PGUSER="test_user"
+  fi
+  export PGUSER
+
+  if [[ "${PGDATABASE:-}" == "" ]]; then
+    PGDATABASE="test_db"
+  fi
+  export PGDATABASE
+
+  if [[ "${postgresqlTestUserOptions:-}" == "" ]]; then
+    postgresqlTestUserOptions="LOGIN"
+  fi
+
+  if [[ "${postgresqlTestSetupSQL:-}" == "" ]]; then
+    postgresqlTestSetupSQL="$(cat <<EOF
+      CREATE ROLE "$PGUSER" $postgresqlTestUserOptions;
+      CREATE DATABASE "$PGDATABASE" OWNER '$PGUSER';
+EOF
+    )"
+  fi
+
+  if [[ "${postgresqlTestSetupCommands:-}" == "" ]]; then
+    postgresqlTestSetupCommands='echo "$postgresqlTestSetupSQL" | PGUSER=postgres psql postgres'
+  fi
+
+  if ! type initdb >/dev/null; then
+    echo >&2 'initdb not found. Did you add postgresql to the nativeCheckInputs?'
+    false
+  fi
+  echo 'initializing postgresql'
+  initdb -U postgres
+
+  echo "$postgresqlExtraSettings" >>"$PGDATA/postgresql.conf"
+
+  # Move the socket
+  echo "unix_socket_directories = '$NIX_BUILD_TOP/run/postgresql'" >>"$PGDATA/postgresql.conf"
+
+  # TCP ports can be a problem in some sandboxes,
+  # so we disable tcp listening by default
+  if ! [[ "${postgresqlEnableTCP:-}" = 1 ]]; then
+    echo "listen_addresses = ''" >>"$PGDATA/postgresql.conf"
+  fi
+
+  echo 'starting postgresql'
+  eval "${postgresqlStartCommands:-pg_ctl start}"
+
+  echo 'setting up postgresql'
+  eval "$postgresqlTestSetupCommands"
+
+  runHook postgresqlTestSetupPost
+
+}
+
+postgresqlStop() {
+  echo 'stopping postgresql'
+  pg_ctl stop
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/test.nix b/nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/test.nix
new file mode 100644
index 000000000000..9881ed1016cc
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/postgresql-test-hook/test.nix
@@ -0,0 +1,30 @@
+{ postgresql, postgresqlTestHook, stdenv }:
+
+stdenv.mkDerivation {
+  name = "postgresql-test-hook-test";
+  buildInputs = [ postgresqlTestHook ];
+  nativeCheckInputs = [ postgresql ];
+  dontUnpack = true;
+  doCheck = true;
+  passAsFile = ["sql"];
+  sql = ''
+    CREATE TABLE hello (
+      message text
+    );
+    INSERT INTO hello VALUES ('it '||'worked');
+    SELECT * FROM hello;
+  '';
+  postgresqlTestSetupPost = ''
+    TEST_POST_HOOK_RAN=1
+  '';
+  checkPhase = ''
+    runHook preCheck
+    psql <$sqlPath | grep 'it worked'
+    TEST_RAN=1
+    runHook postCheck
+  '';
+  installPhase = ''
+    [[ $TEST_RAN == 1 && $TEST_POST_HOOK_RAN == 1 ]]
+    touch $out
+  '';
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/prune-libtool-files.sh b/nixpkgs/pkgs/build-support/setup-hooks/prune-libtool-files.sh
new file mode 100644
index 000000000000..0ec56549645c
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/prune-libtool-files.sh
@@ -0,0 +1,22 @@
+# Clear dependency_libs in libtool files for shared libraries.
+
+# Shared libraries already encode their dependencies with locations.  .la
+# files do not always encode those locations, and sometimes encode the
+# locations in the wrong Nix output. .la files are not needed for shared
+# libraries, but without dependency_libs they do not hurt either.
+
+fixupOutputHooks+=(_pruneLibtoolFiles)
+
+_pruneLibtoolFiles() {
+    if [ "${dontPruneLibtoolFiles-}" ] || [ ! -e "$prefix" ]; then
+       return
+    fi
+
+    # Libtool uses "dlname" and "library_names" fields for shared libraries and
+    # the "old_library" field for static libraries.  We are processing only
+    # those .la files that do not describe static libraries.
+    find "$prefix" -type f -name '*.la' \
+         -exec grep -q '^# Generated by .*libtool' {} \; \
+         -exec grep -q "^old_library=''" {} \; \
+         -exec sed -i {} -e "/^dependency_libs='[^']/ c dependency_libs='' #pruned" \;
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/reproducible-builds.sh b/nixpkgs/pkgs/build-support/setup-hooks/reproducible-builds.sh
new file mode 100644
index 000000000000..5e27ce8a25fe
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/reproducible-builds.sh
@@ -0,0 +1,11 @@
+# Use the last part of the out path as hash input for the build.
+# This should ensure that it is deterministic across rebuilds of the same
+# derivation and not easily collide with other builds.
+# We also truncate the hash so that it cannot cause reference cycles.
+NIX_CFLAGS_COMPILE="${NIX_CFLAGS_COMPILE:-} -frandom-seed=$(
+    randSeed=${NIX_OUTPATH_USED_AS_RANDOM_SEED:-$out}
+    outbase="${randSeed##*/}"
+    randomseed="${outbase:0:10}"
+    echo $randomseed
+)"
+export NIX_CFLAGS_COMPILE
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/role.bash b/nixpkgs/pkgs/build-support/setup-hooks/role.bash
new file mode 100644
index 000000000000..bfd6b61f0aed
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/role.bash
@@ -0,0 +1,71 @@
+# Since the same derivation can be depended on in multiple ways, we need to
+# accumulate *each* role (i.e. host and target platforms relative the depending
+# derivation) in which the derivation is used.
+#
+# The role is intended to be used as part of other variables names like
+#  - $NIX_SOMETHING${role_post}
+
+function getRole() {
+    case $1 in
+        -1)
+            role_post='_FOR_BUILD'
+            ;;
+        0)
+            role_post=''
+            ;;
+        1)
+            role_post='_FOR_TARGET'
+            ;;
+        *)
+            echo "@name@: used as improper sort of dependency" >&2
+            return 1
+            ;;
+    esac
+}
+
+# `hostOffset` describes how the host platform of the package is slid relative
+# to the depending package. `targetOffset` likewise describes the target
+# platform of the package. Both are brought into scope of the setup hook defined
+# for dependency whose setup hook is being processed relative to the package
+# being built.
+
+function getHostRole()   {
+    getRole "$hostOffset"
+}
+function getTargetRole() {
+    getRole "$targetOffset"
+}
+
+# `depHostOffset` describes how the host platform of the dependencies are slid
+# relative to the depending package. `depTargetOffset` likewise describes the
+# target platform of dependenices. Both are brought into scope of the
+# environment hook defined for the dependency being applied relative to the
+# package being built.
+
+function getHostRoleEnvHook()   {
+    getRole "$depHostOffset"
+}
+function getTargetRoleEnvHook() {
+    getRole "$depTargetOffset"
+}
+
+# This variant is intended specifically for code-producing tool wrapper scripts
+# `NIX_@wrapperName@_TARGET_*_@suffixSalt@` tracks this (needs to be an exported
+# env var so can't use fancier data structures).
+function getTargetRoleWrapper() {
+    case $targetOffset in
+        -1)
+            export NIX_@wrapperName@_TARGET_BUILD_@suffixSalt@=1
+            ;;
+        0)
+            export NIX_@wrapperName@_TARGET_HOST_@suffixSalt@=1
+            ;;
+        1)
+            export NIX_@wrapperName@_TARGET_TARGET_@suffixSalt@=1
+            ;;
+        *)
+            echo "@name@: used as improper sort of dependency" >&2
+            return 1
+            ;;
+    esac
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/separate-debug-info.sh b/nixpkgs/pkgs/build-support/setup-hooks/separate-debug-info.sh
new file mode 100644
index 000000000000..197e8a920b70
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/separate-debug-info.sh
@@ -0,0 +1,52 @@
+export NIX_SET_BUILD_ID=1
+export NIX_LDFLAGS+=" --compress-debug-sections=zlib"
+export NIX_CFLAGS_COMPILE+=" -ggdb -Wa,--compress-debug-sections"
+export NIX_RUSTFLAGS+=" -g"
+
+fixupOutputHooks+=(_separateDebugInfo)
+
+_separateDebugInfo() {
+    [ -e "$prefix" ] || return 0
+
+    local dst="${debug:-$out}"
+    if [ "$prefix" = "$dst" ]; then return 0; fi
+
+    # in case there is nothing to strip, don't fail the build
+    mkdir -p "$dst"
+
+    dst="$dst/lib/debug/.build-id"
+
+    # Find executables and dynamic libraries.
+    local i
+    while IFS= read -r -d $'\0' i; do
+        if ! isELF "$i"; then continue; fi
+
+        [ -z "${READELF:-}" ] && echo "_separateDebugInfo: '\$READELF' variable is empty, skipping." 1>&2 && break
+        [ -z "${OBJCOPY:-}" ] && echo "_separateDebugInfo: '\$OBJCOPY' variable is empty, skipping." 1>&2 && break
+
+        # Extract the Build ID. FIXME: there's probably a cleaner way.
+        local id="$($READELF -n "$i" | sed 's/.*Build ID: \([0-9a-f]*\).*/\1/; t; d')"
+        if [ "${#id}" != 40 ]; then
+            echo "could not find build ID of $i, skipping" >&2
+            continue
+        fi
+
+        # Extract the debug info.
+        echo "separating debug info from $i (build ID $id)"
+        mkdir -p "$dst/${id:0:2}"
+
+        # This may fail, e.g. if the binary is for a different
+        # architecture than we're building for.  (This happens with
+        # firmware blobs in QEMU.)
+        (
+            if [ -f "$dst/${id:0:2}/${id:2}.debug" ]
+            then
+                echo "separate-debug-info: warning: multiple files with build id $id found, overwriting"
+            fi
+            $OBJCOPY --only-keep-debug "$i" "$dst/${id:0:2}/${id:2}.debug"
+
+            # Also a create a symlink <original-name>.debug.
+            ln -sfn ".build-id/${id:0:2}/${id:2}.debug" "$dst/../$(basename "$i")"
+        ) || rmdir -p "$dst/${id:0:2}"
+    done < <(find "$prefix" -type f -print0 | sort -z)
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/set-java-classpath.sh b/nixpkgs/pkgs/build-support/setup-hooks/set-java-classpath.sh
new file mode 100644
index 000000000000..445fa56d61de
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/set-java-classpath.sh
@@ -0,0 +1,13 @@
+# This setup hook adds every JAR in the share/java subdirectories of
+# the build inputs to $CLASSPATH.
+
+export CLASSPATH
+
+addPkgToClassPath () {
+    local jar
+    for jar in $1/share/java/*.jar; do
+        export CLASSPATH=''${CLASSPATH-}''${CLASSPATH:+:}''${jar}
+    done
+}
+
+addEnvHooks "$targetOffset" addPkgToClassPath
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/set-source-date-epoch-to-latest.sh b/nixpkgs/pkgs/build-support/setup-hooks/set-source-date-epoch-to-latest.sh
new file mode 100644
index 000000000000..ae34ffec4854
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/set-source-date-epoch-to-latest.sh
@@ -0,0 +1,34 @@
+updateSourceDateEpoch() {
+    local path="$1"
+
+    # Get the last modification time of all regular files, sort them,
+    # and get the most recent. Maybe we should use
+    # https://github.com/0-wiz-0/findnewest here.
+    local -a res=($(find "$path" -type f -not -newer "$NIX_BUILD_TOP/.." -printf '%T@ %p\0' \
+                    | sort -n --zero-terminated | tail -n1 --zero-terminated | head -c -1))
+    local time="${res[0]//\.[0-9]*/}" # remove the fraction part
+    local newestFile="${res[1]}"
+
+    # Update $SOURCE_DATE_EPOCH if the most recent file we found is newer.
+    if [ "${time:-0}" -gt "$SOURCE_DATE_EPOCH" ]; then
+        echo "setting SOURCE_DATE_EPOCH to timestamp $time of file $newestFile"
+        export SOURCE_DATE_EPOCH="$time"
+
+        # Warn if the new timestamp is too close to the present. This
+        # may indicate that we were being applied to a file generated
+        # during the build, or that an unpacker didn't restore
+        # timestamps properly.
+        local now="$(date +%s)"
+        if [ "$time" -gt $((now - 60)) ]; then
+            echo "warning: file $newestFile may be generated; SOURCE_DATE_EPOCH may be non-deterministic"
+        fi
+    fi
+}
+
+postUnpackHooks+=(_updateSourceDateEpochFromSourceRoot)
+
+_updateSourceDateEpochFromSourceRoot() {
+    if [ -n "$sourceRoot" ]; then
+        updateSourceDateEpoch "$sourceRoot"
+    fi
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/setup-debug-info-dirs.sh b/nixpkgs/pkgs/build-support/setup-hooks/setup-debug-info-dirs.sh
new file mode 100644
index 000000000000..96bf48cf123a
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/setup-debug-info-dirs.sh
@@ -0,0 +1,5 @@
+setupDebugInfoDirs () {
+    addToSearchPath NIX_DEBUG_INFO_DIRS $1/lib/debug
+}
+
+addEnvHooks "$targetOffset" setupDebugInfoDirs
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/shorten-perl-shebang.sh b/nixpkgs/pkgs/build-support/setup-hooks/shorten-perl-shebang.sh
new file mode 100644
index 000000000000..825da1bde962
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/shorten-perl-shebang.sh
@@ -0,0 +1,88 @@
+# This setup hook modifies a Perl script so that any "-I" flags in its shebang
+# line are rewritten into a "use lib ..." statement on the next line. This gets
+# around a limitation in Darwin, which will not properly handle a script whose
+# shebang line exceeds 511 characters.
+#
+# Each occurrence of "-I /path/to/lib1" or "-I/path/to/lib2" is removed from
+# the shebang line, along with the single space that preceded it. These library
+# paths are placed into a new line of the form
+#
+#     use lib "/path/to/lib1", "/path/to/lib2";
+#
+# immediately following the shebang line. If a library appeared in the original
+# list more than once, only its first occurrence will appear in the output
+# list. In other words, the libraries are deduplicated, but the ordering of the
+# first appearance of each one is preserved.
+#
+# Any flags other than "-I" in the shebang line are left as-is, and the
+# interpreter is also left alone (although the script will abort if the
+# interpreter does not seem to be either "perl" or else "env" with "perl" as
+# its argument). Each line after the shebang line is left unchanged. Each file
+# is modified in place.
+#
+# Usage:
+#     shortenPerlShebang SCRIPT...
+
+shortenPerlShebang() {
+    while [ $# -gt 0 ]; do
+        _shortenPerlShebang "$1"
+        shift
+    done
+}
+
+_shortenPerlShebang() {
+    local program="$1"
+
+    echo "shortenPerlShebang: rewriting shebang line in $program"
+
+    if ! isScript "$program"; then
+        die "shortenPerlShebang: refusing to modify $program because it is not a script"
+    fi
+
+    local temp="$(mktemp)"
+
+    gawk '
+        (NR == 1) {
+            if (!($0 ~ /\/(perl|env +perl)\>/)) {
+                print "shortenPerlShebang: script does not seem to be a Perl script" > "/dev/stderr"
+                exit 1
+            }
+            idx = 0
+            while (match($0, / -I ?([^ ]+)/, pieces)) {
+                matches[idx] = pieces[1]
+                idx++
+                $0 = gensub(/ -I ?[^ ]+/, "", 1, $0)
+            }
+            print $0
+            if (idx > 0) {
+                prefix = "use lib "
+                for (idx in matches) {
+                    path = matches[idx]
+                    if (!(path in seen)) {
+                        printf "%s\"%s\"", prefix, path
+                        seen[path] = 1
+                        prefix = ", "
+                    }
+                }
+                print ";"
+            }
+        }
+        (NR > 1 ) {
+            print
+        }
+    ' "$program" > "$temp" || die
+    # Preserve the mode of the original file
+    cp --preserve=mode --attributes-only "$program" "$temp"
+    mv "$temp" "$program"
+
+    # Measure the new shebang line length and make sure it's okay. We subtract
+    # one to account for the trailing newline that "head" included in its
+    # output.
+    local new_length=$(( $(head -n 1 "$program" | wc -c) - 1 ))
+
+    # Darwin is okay when the shebang line contains 511 characters, but not
+    # when it contains 512 characters.
+    if [ $new_length -ge 512 ]; then
+        die "shortenPerlShebang: shebang line is $new_length characters--still too long for Darwin!"
+    fi
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/strip-java-archives.sh b/nixpkgs/pkgs/build-support/setup-hooks/strip-java-archives.sh
new file mode 100644
index 000000000000..22322468f76d
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/strip-java-archives.sh
@@ -0,0 +1,16 @@
+# This setup hook makes the fixup phase to repack all java archives in a
+# deterministic fashion. The most important change being done is the resetting
+# of the modification times of the archive entries
+
+fixupOutputHooks+=('stripJavaArchivesIn $prefix')
+
+stripJavaArchivesIn() {
+    local dir="$1"
+    echo "stripping java archives in $dir"
+    find $dir -type f -regextype posix-egrep -regex ".*\.(jar|war|hpi|apk)$" -print0 |
+    while IFS= read -rd '' f; do
+        echo "stripping java archive $f"
+        strip-nondeterminism --type jar "$f"
+    done
+}
+
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/strip.sh b/nixpkgs/pkgs/build-support/setup-hooks/strip.sh
new file mode 100644
index 000000000000..49a350af1fa5
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/strip.sh
@@ -0,0 +1,102 @@
+# This setup hook strips libraries and executables in the fixup phase.
+
+fixupOutputHooks+=(_doStrip)
+
+_doStrip() {
+    # We don't bother to strip build platform code because it shouldn't make it
+    # to $out anyways---if it does, that's a bigger problem that a lack of
+    # stripping will help catch.
+    local -ra flags=(dontStripHost dontStripTarget)
+    local -ra debugDirs=(stripDebugList stripDebugListTarget)
+    local -ra allDirs=(stripAllList stripAllListTarget)
+    local -ra stripCmds=(STRIP STRIP_FOR_TARGET)
+    local -ra ranlibCmds=(RANLIB RANLIB_FOR_TARGET)
+
+    # TODO(structured-attrs): This doesn't work correctly if one of
+    #   the items in strip*List or strip*Flags contains a space,
+    #   even with structured attrs enabled.  This is OK for now
+    #   because very few packages set any of these, and it doesn't
+    #   affect any of them.
+    #
+    #   After __structuredAttrs = true is universal, come back and
+    #   push arrays all the way through this logic.
+
+    # Strip only host paths by default. Leave targets as is.
+    stripDebugList=${stripDebugList[*]:-lib lib32 lib64 libexec bin sbin}
+    stripDebugListTarget=${stripDebugListTarget[*]:-}
+    stripAllList=${stripAllList[*]:-}
+    stripAllListTarget=${stripAllListTarget[*]:-}
+
+    local i
+    for i in ${!stripCmds[@]}; do
+        local -n flag="${flags[$i]}"
+        local -n debugDirList="${debugDirs[$i]}"
+        local -n allDirList="${allDirs[$i]}"
+        local -n stripCmd="${stripCmds[$i]}"
+        local -n ranlibCmd="${ranlibCmds[$i]}"
+
+        # `dontStrip` disables them all
+        if [[ "${dontStrip-}" || "${flag-}" ]] || ! type -f "${stripCmd-}" 2>/dev/null 1>&2
+        then continue; fi
+
+        stripDirs "$stripCmd" "$ranlibCmd" "$debugDirList" "${stripDebugFlags[*]:--S -p}"
+        stripDirs "$stripCmd" "$ranlibCmd" "$allDirList" "${stripAllFlags[*]:--s -p}"
+    done
+}
+
+stripDirs() {
+    local cmd="$1"
+    local ranlibCmd="$2"
+    local paths="$3"
+    local stripFlags="$4"
+    local excludeFlags=()
+    local pathsNew=
+
+    [ -z "$cmd" ] && echo "stripDirs: Strip command is empty" 1>&2 && exit 1
+    [ -z "$ranlibCmd" ] && echo "stripDirs: Ranlib command is empty" 1>&2 && exit 1
+
+    local pattern
+    if [ -n "${stripExclude:-}" ]; then
+        for pattern in "${stripExclude[@]}"; do
+            excludeFlags+=(-a '!' '(' -name "$pattern" -o -wholename "$prefix/$pattern" ')' )
+        done
+    fi
+
+    local p
+    for p in ${paths}; do
+        if [ -e "$prefix/$p" ]; then
+            pathsNew="${pathsNew} $prefix/$p"
+        fi
+    done
+    paths=${pathsNew}
+
+    if [ -n "${paths}" ]; then
+        echo "stripping (with command $cmd and flags $stripFlags) in $paths"
+        local striperr
+        striperr="$(mktemp --tmpdir="$TMPDIR" 'striperr.XXXXXX')"
+        # Make sure we process files only once. `strip`ping the same file through different
+        # links in parallel can corrupt it:
+        #   https://github.com/NixOS/nixpkgs/issues/246147#issuecomment-1657072039
+
+        # Do not strip lib/debug. This is a directory used by setup-hooks/separate-debug-info.sh.
+        # Print out each file's device and inode (which will be the same if two files are hardlinked
+        # or are the same file found through different symlinks), followed by its path...
+        find $paths -type f "${excludeFlags[@]}" -a '!' -path "$prefix/lib/debug/*" -printf '%D-%i,%p\0' |
+            # ... sort/uniq by device/inode, then cut them out and keep the path, ...
+            sort -t, -k1,1 -u -z | cut -d, -f2- -z |
+            # and finally strip each unique path in parallel.
+            xargs -r -0 -n1 -P "$NIX_BUILD_CORES" -- $cmd $stripFlags 2>"$striperr" || exit_code=$?
+        # xargs exits with status code 123 if some but not all of the
+        # processes fail. We don't care if some of the files couldn't
+        # be stripped, so ignore specifically this code.
+        [[ "$exit_code" = 123 || -z "$exit_code" ]] || (cat "$striperr" 1>&2 && exit 1)
+
+        rm "$striperr"
+        # 'strip' does not normally preserve archive index in .a files.
+        # This usually causes linking failures against static libs like:
+        #   ld: ...-i686-w64-mingw32-stage-final-gcc-13.0.0-lib/i686-w64-mingw32/lib/libstdc++.dll.a:
+        #     error adding symbols: archive has no index; run ranlib to add one
+        # Restore the index by running 'ranlib'.
+        find $paths -name '*.a' -type f -exec $ranlibCmd '{}' \; 2>/dev/null
+    fi
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh b/nixpkgs/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh
new file mode 100644
index 000000000000..ebd3afa05d94
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/update-autotools-gnu-config-scripts.sh
@@ -0,0 +1,12 @@
+preConfigurePhases+=" updateAutotoolsGnuConfigScriptsPhase"
+
+updateAutotoolsGnuConfigScriptsPhase() {
+    if [ -n "${dontUpdateAutotoolsGnuConfigScripts-}" ]; then return; fi
+
+    for script in config.sub config.guess; do
+        for f in $(find . -type f -name "$script"); do
+            echo "Updating Autotools / GNU config script to a newer upstream version: $f"
+            cp -f "@gnu_config@/$script" "$f"
+        done
+    done
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/use-old-cxx-abi.sh b/nixpkgs/pkgs/build-support/setup-hooks/use-old-cxx-abi.sh
new file mode 100644
index 000000000000..53335d7a9a7a
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/use-old-cxx-abi.sh
@@ -0,0 +1 @@
+export NIX_CFLAGS_COMPILE+=" -D_GLIBCXX_USE_CXX11_ABI=0"
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/validate-pkg-config.sh b/nixpkgs/pkgs/build-support/setup-hooks/validate-pkg-config.sh
new file mode 100644
index 000000000000..c212a1f5301a
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/validate-pkg-config.sh
@@ -0,0 +1,18 @@
+# This setup hook validates each pkgconfig file in each output.
+
+fixupOutputHooks+=(_validatePkgConfig)
+
+_validatePkgConfig() {
+    local bail=0
+    for pc in $(find "$prefix" -name '*.pc'); do
+        # Do not fail immediately. It's nice to see all errors when
+        # there are multiple pkgconfig files.
+        if ! $PKG_CONFIG --validate "$pc"; then
+            bail=1
+        fi
+    done
+
+    if [ $bail -eq 1 ]; then
+        exit 1
+    fi
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/win-dll-link.sh b/nixpkgs/pkgs/build-support/setup-hooks/win-dll-link.sh
new file mode 100644
index 000000000000..14594bcba937
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/win-dll-link.sh
@@ -0,0 +1,89 @@
+fixupOutputHooks+=(_linkDLLs)
+
+addEnvHooks "$targetOffset" linkDLLGetFolders
+
+linkDLLGetFolders() {
+    addToSearchPath "LINK_DLL_FOLDERS" "$1/lib"
+    addToSearchPath "LINK_DLL_FOLDERS" "$1/bin"
+}
+
+_linkDLLs() {
+    linkDLLsInfolder "$prefix/bin"
+}
+
+# Try to links every known dependency of exe/dll in the folder of the 1str input
+# into said folder, so they are found on invocation.
+# (DLLs are first searched in the directory of the running exe file.)
+# The links are relative, so relocating whole /nix/store won't break them.
+linkDLLsInfolder() {
+    (
+        local folder
+        folder="$1"
+        if [ ! -d "$folder" ]; then
+            echo "Not linking DLLs in the non-existent folder $folder"
+            return
+        fi
+        cd "$folder" || exit
+
+        # Use associative arrays as set
+        local filesToChecks
+        local filesDone
+        declare -A filesToChecks # files that still needs to have their dependancies checked
+        declare -A filesDone     # files that had their dependancies checked and who is copied to the bin folder if found
+
+        markFileAsDone() {
+            if [ ! "${filesDone[$1]+a}" ]; then filesDone[$1]=a; fi
+            if [ "${filesToChecks[$1]+a}" ]; then unset 'filesToChecks[$1]'; fi
+        }
+
+        addFileToLink() {
+            if [ "${filesDone[$1]+a}" ]; then return; fi
+            if [ ! "${filesToChecks[$1]+a}" ]; then filesToChecks[$1]=a; fi
+        }
+
+        # Compose path list where DLLs should be located:
+        #   prefix $PATH by currently-built outputs
+        local DLLPATH=""
+        local outName
+        for outName in $(getAllOutputNames); do
+            addToSearchPath DLLPATH "${!outName}/bin"
+        done
+        DLLPATH="$DLLPATH:$LINK_DLL_FOLDERS"
+
+        echo DLLPATH="'$DLLPATH'"
+
+        for peFile in *.{exe,dll}; do
+            if [ -e "./$peFile" ]; then
+                addFileToLink "$peFile"
+            fi
+        done
+
+        local searchPaths
+        readarray -td: searchPaths < <(printf -- "%s" "$DLLPATH")
+
+        local linkCount=0
+        while [ ${#filesToChecks[*]} -gt 0 ]; do
+            local listOfDlls=("${!filesToChecks[@]}")
+            local file=${listOfDlls[0]}
+            markFileAsDone "$file"
+            if [ ! -e "./$file" ]; then
+                local pathsFound
+                readarray -d '' pathsFound < <(find "${searchPaths[@]}" -name "$file" -type f -print0)
+                if [ ${#pathsFound[@]} -eq 0 ]; then continue; fi
+                local dllPath
+                dllPath="${pathsFound[0]}"
+                CYGWIN+=" winsymlinks:nativestrict" ln -sr "$dllPath" .
+                echo "linking $dllPath"
+                file="$dllPath"
+                linkCount=$((linkCount + 1))
+            fi
+            # local dep_file
+            # Look at the file’s dependancies
+            for dep_file in $($OBJDUMP -p "$file" | sed -n 's/.*DLL Name: \(.*\)/\1/p' | sort -u); do
+                addFileToLink "$dep_file"
+            done
+        done
+
+        echo "Created $linkCount DLL link(s) in $folder"
+    )
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix b/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix
new file mode 100644
index 000000000000..69f9f3b145d7
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix
@@ -0,0 +1,203 @@
+{ stdenv
+, lib
+, makeSetupHook
+, makeWrapper
+, gobject-introspection
+, isGraphical ? false
+, gtk3
+, librsvg
+, dconf
+, callPackage
+, wrapGAppsHook3
+, targetPackages
+}:
+
+makeSetupHook {
+  name = "wrap-gapps-hook";
+  propagatedBuildInputs = [
+    # We use the wrapProgram function.
+    makeWrapper
+  ] ++ lib.optionals isGraphical [
+    # TODO: remove this, packages should depend on GTK explicitly.
+    gtk3
+
+    librsvg
+  ];
+
+  # depsTargetTargetPropagated will essentially be buildInputs when wrapGAppsHook3 is placed into nativeBuildInputs
+  # the librsvg and gtk3 above should be removed but kept to not break anything that implicitly depended on its binaries
+  depsTargetTargetPropagated = assert (lib.assertMsg (!targetPackages ? raw) "wrapGAppsHook3 must be in nativeBuildInputs"); lib.optionals isGraphical [
+    # librsvg provides a module for gdk-pixbuf to allow rendering
+    # SVG icons. Most icon themes are SVG-based and so are some
+    # graphics in GTK (e.g. cross for closing window in window title bar)
+    # so it is pretty much required for applications using GTK.
+    librsvg
+
+    # TODO: remove this, packages should depend on GTK explicitly.
+    gtk3
+  ] ++ lib.optionals (!stdenv.isDarwin) [
+    # It is highly probable that a program will use GSettings,
+    # at minimum through GTK file chooser dialogue.
+    # Let’s add a GIO module for “dconf” GSettings backend
+    # to avoid falling back to “memory” backend. This is
+    # required for GSettings-based settings to be persisted.
+    # Unfortunately, it also requires the user to have dconf
+    # D-Bus service enabled globally (e.g. through a NixOS module).
+    dconf.lib
+  ];
+  passthru = {
+    tests = let
+      sample-project = ./tests/sample-project;
+
+      testLib = callPackage ./tests/lib.nix { };
+      inherit (testLib) expectSomeLineContainingYInFileXToMentionZ;
+    in rec {
+      # Simple derivation containing a program and a daemon.
+      basic = stdenv.mkDerivation {
+        name = "basic";
+
+        src = sample-project;
+
+        strictDeps = true;
+        nativeBuildInputs = [ wrapGAppsHook3 ];
+
+        installFlags = [ "bin-foo" "libexec-bar" ];
+      };
+
+      # The wrapper for executable files should add path to dconf GIO module.
+      basic-contains-dconf = let
+        tested = basic;
+      in testLib.runTest "basic-contains-dconf" (
+        testLib.skip stdenv.isDarwin ''
+          ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GIO_EXTRA_MODULES" "${dconf.lib}/lib/gio/modules"}
+          ${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GIO_EXTRA_MODULES" "${dconf.lib}/lib/gio/modules"}
+        ''
+      );
+
+      basic-contains-gdk-pixbuf = let
+        tested = basic;
+      in testLib.runTest "basic-contains-gdk-pixbuf" (
+        testLib.skip stdenv.isDarwin ''
+          ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GDK_PIXBUF_MODULE_FILE" "${lib.getLib librsvg}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"}
+          ${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GDK_PIXBUF_MODULE_FILE" "${lib.getLib librsvg}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"}
+        ''
+      );
+
+      # Simple derivation containing a gobject-introspection typelib.
+      typelib-Mahjong = stdenv.mkDerivation {
+        name = "typelib-Mahjong";
+
+        src = sample-project;
+
+        strictDeps = true;
+
+        installFlags = [ "typelib-Mahjong" ];
+      };
+
+      # Simple derivation using a typelib.
+      typelib-user = stdenv.mkDerivation {
+        name = "typelib-user";
+
+        src = sample-project;
+
+        strictDeps = true;
+        nativeBuildInputs = [
+          gobject-introspection
+          wrapGAppsHook3
+        ];
+
+        buildInputs = [
+          typelib-Mahjong
+        ];
+
+        installFlags = [ "bin-foo" "libexec-bar" ];
+      };
+
+      # Testing cooperation with gobject-introspection setup hook,
+      # which should populate GI_TYPELIB_PATH variable with paths
+      # to typelibs among the derivation’s dependencies.
+      # The resulting GI_TYPELIB_PATH should be picked up by the wrapper.
+      typelib-user-has-gi-typelib-path = let
+        tested = typelib-user;
+      in testLib.runTest "typelib-user-has-gi-typelib-path" ''
+        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GI_TYPELIB_PATH" "${typelib-Mahjong}/lib/girepository-1.0"}
+        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GI_TYPELIB_PATH" "${typelib-Mahjong}/lib/girepository-1.0"}
+      '';
+
+      # Simple derivation containing a gobject-introspection typelib in lib output.
+      typelib-Bechamel = stdenv.mkDerivation {
+        name = "typelib-Bechamel";
+
+        outputs = [ "out" "lib" ];
+
+        src = sample-project;
+
+        strictDeps = true;
+
+        makeFlags = [
+          "LIBDIR=${placeholder "lib"}/lib"
+        ];
+
+        installFlags = [ "typelib-Bechamel" ];
+      };
+
+      # Simple derivation using a typelib from non-default output.
+      typelib-multiout-user = stdenv.mkDerivation {
+        name = "typelib-multiout-user";
+
+        src = sample-project;
+
+        strictDeps = true;
+        nativeBuildInputs = [
+          gobject-introspection
+          wrapGAppsHook3
+        ];
+
+        buildInputs = [
+          typelib-Bechamel
+        ];
+
+        installFlags = [ "bin-foo" "libexec-bar" ];
+      };
+
+      # Testing cooperation with gobject-introspection setup hook,
+      # which should populate GI_TYPELIB_PATH variable with paths
+      # to typelibs among the derivation’s dependencies,
+      # even when they are not in default output.
+      # The resulting GI_TYPELIB_PATH should be picked up by the wrapper.
+      typelib-multiout-user-has-gi-typelib-path = let
+        tested = typelib-multiout-user;
+      in testLib.runTest "typelib-multiout-user-has-gi-typelib-path" ''
+        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GI_TYPELIB_PATH" "${typelib-Bechamel.lib}/lib/girepository-1.0"}
+        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GI_TYPELIB_PATH" "${typelib-Bechamel.lib}/lib/girepository-1.0"}
+      '';
+
+      # Simple derivation that contains a typelib as well as a program using it.
+      typelib-self-user = stdenv.mkDerivation {
+        name = "typelib-self-user";
+
+        src = sample-project;
+
+        strictDeps = true;
+        nativeBuildInputs = [
+          gobject-introspection
+          wrapGAppsHook3
+        ];
+
+        installFlags = [ "typelib-Cow" "bin-foo" "libexec-bar" ];
+      };
+
+      # Testing cooperation with gobject-introspection setup hook,
+      # which should add the path to derivation’s own typelibs
+      # to GI_TYPELIB_PATH variable.
+      # The resulting GI_TYPELIB_PATH should be picked up by the wrapper.
+      # https://github.com/NixOS/nixpkgs/issues/85515
+      typelib-self-user-has-gi-typelib-path = let
+        tested = typelib-self-user;
+      in testLib.runTest "typelib-self-user-has-gi-typelib-path" ''
+        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GI_TYPELIB_PATH" "${typelib-self-user}/lib/girepository-1.0"}
+        ${expectSomeLineContainingYInFileXToMentionZ "${tested}/libexec/bar" "GI_TYPELIB_PATH" "${typelib-self-user}/lib/girepository-1.0"}
+      '';
+    };
+  };
+} ./wrap-gapps-hook.sh
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/tests/lib.nix b/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/tests/lib.nix
new file mode 100644
index 000000000000..59fa9de24f9d
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/tests/lib.nix
@@ -0,0 +1,31 @@
+{ lib, runCommand }:
+
+rec {
+  runTest = name: body: runCommand name { strictDeps = true; } ''
+    set -o errexit
+    ${body}
+    touch $out
+  '';
+
+  skip = cond: text:
+    if cond then ''
+      echo "Skipping test $name" > /dev/stderr
+    '' else text;
+
+  fail = text: ''
+    echo "FAIL: $name: ${text}" > /dev/stderr
+    exit 1
+  '';
+
+  expectSomeLineContainingYInFileXToMentionZ = file: filter: expected: ''
+    file=${lib.escapeShellArg file} filter=${lib.escapeShellArg filter} expected=${lib.escapeShellArg expected}
+
+    if ! grep --text --quiet "$filter" "$file"; then
+        ${fail "The file “$file” should include a line containing “$filter”."}
+    fi
+
+    if ! grep --text "$filter" "$file" | grep --text --quiet "$expected"; then
+        ${fail "The file “$file” should include a line containing “$filter” that also contains “$expected”."}
+    fi
+  '';
+}
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/tests/sample-project/Makefile b/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/tests/sample-project/Makefile
new file mode 100644
index 000000000000..5d234db11a0b
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/tests/sample-project/Makefile
@@ -0,0 +1,30 @@
+PREFIX = $(out)
+BINDIR = $(PREFIX)/bin
+LIBEXECDIR = $(PREFIX)/libexec
+LIBDIR = $(PREFIX)/lib
+TYPELIBDIR = $(LIBDIR)/girepository-1.0
+
+all:
+	echo "Compiling…"
+install:
+	echo "Installing…"
+
+bin:
+	mkdir -p $(BINDIR)
+# Adds `bin-${foo}` targets, that install `${foo}` executable to `$(BINDIR)`.
+bin-%: bin
+	touch $(BINDIR)/$(@:bin-%=%)
+	chmod +x $(BINDIR)/$(@:bin-%=%)
+
+libexec:
+	mkdir -p $(LIBEXECDIR)
+# Adds `libexec-${foo}` targets, that install `${foo}` executable to `$(LIBEXECDIR)`.
+libexec-%: libexec
+	touch $(LIBEXECDIR)/$(@:libexec-%=%)
+	chmod +x $(LIBEXECDIR)/$(@:libexec-%=%)
+
+typelib:
+	mkdir -p $(TYPELIBDIR)
+# Adds `typelib-${foo}` targets, that install `${foo}-1.0.typelib` file to `$(TYPELIBDIR)`.
+typelib-%: typelib
+	touch $(TYPELIBDIR)/$(@:typelib-%=%)-1.0.typelib
diff --git a/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh b/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh
new file mode 100644
index 000000000000..0acf4a8e6f8d
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh
@@ -0,0 +1,89 @@
+# shellcheck shell=bash
+gappsWrapperArgs=()
+
+find_gio_modules() {
+    if [ -d "$1/lib/gio/modules" ] && [ -n "$(ls -A "$1/lib/gio/modules")" ] ; then
+        gappsWrapperArgs+=(--prefix GIO_EXTRA_MODULES : "$1/lib/gio/modules")
+    fi
+}
+
+addEnvHooks "${targetOffset:?}" find_gio_modules
+
+gappsWrapperArgsHook() {
+    if [ -n "$GDK_PIXBUF_MODULE_FILE" ]; then
+        gappsWrapperArgs+=(--set GDK_PIXBUF_MODULE_FILE "$GDK_PIXBUF_MODULE_FILE")
+    fi
+
+    if [ -n "$GSETTINGS_SCHEMAS_PATH" ]; then
+        gappsWrapperArgs+=(--prefix XDG_DATA_DIRS : "$GSETTINGS_SCHEMAS_PATH")
+    fi
+
+    # Check for prefix as well
+    if [ -d "${prefix:?}/share" ]; then
+        gappsWrapperArgs+=(--prefix XDG_DATA_DIRS : "$prefix/share")
+    fi
+
+    if [ -d "$prefix/lib/gio/modules" ] && [ -n "$(ls -A "$prefix/lib/gio/modules")" ]; then
+        gappsWrapperArgs+=(--prefix GIO_EXTRA_MODULES : "$prefix/lib/gio/modules")
+    fi
+
+    for v in ${wrapPrefixVariables:-} GST_PLUGIN_SYSTEM_PATH_1_0 GI_TYPELIB_PATH GRL_PLUGIN_PATH; do
+        if [ -n "${!v}" ]; then
+            gappsWrapperArgs+=(--prefix "$v" : "${!v}")
+        fi
+    done
+}
+
+preFixupPhases+=" gappsWrapperArgsHook"
+
+wrapGApp() {
+    local program="$1"
+    shift 1
+    wrapProgram "$program" "${gappsWrapperArgs[@]}" "$@"
+}
+
+# Note: $gappsWrapperArgs still gets defined even if ${dontWrapGApps-} is set.
+wrapGAppsHook() {
+    # guard against running multiple times (e.g. due to propagation)
+    [ -z "$wrapGAppsHookHasRun" ] || return 0
+    wrapGAppsHookHasRun=1
+
+    if [[ -z "${dontWrapGApps:-}" ]]; then
+        targetDirsThatExist=()
+        targetDirsRealPath=()
+
+        # wrap binaries
+        targetDirs=("${prefix}/bin" "${prefix}/libexec")
+        for targetDir in "${targetDirs[@]}"; do
+            if [[ -d "${targetDir}" ]]; then
+                targetDirsThatExist+=("${targetDir}")
+                targetDirsRealPath+=("$(realpath "${targetDir}")/")
+                find "${targetDir}" -type f -executable -print0 |
+                    while IFS= read -r -d '' file; do
+                        echo "Wrapping program '${file}'"
+                        wrapGApp "${file}"
+                    done
+            fi
+        done
+
+        # wrap links to binaries that point outside targetDirs
+        # Note: links to binaries within targetDirs do not need
+        #       to be wrapped as the binaries have already been wrapped
+        if [[ ${#targetDirsThatExist[@]} -ne 0 ]]; then
+            find "${targetDirsThatExist[@]}" -type l -xtype f -executable -print0 |
+                while IFS= read -r -d '' linkPath; do
+                    linkPathReal=$(realpath "${linkPath}")
+                    for targetPath in "${targetDirsRealPath[@]}"; do
+                        if [[ "$linkPathReal" == "$targetPath"* ]]; then
+                            echo "Not wrapping link: '$linkPath' (already wrapped)"
+                            continue 2
+                        fi
+                    done
+                    echo "Wrapping link: '$linkPath'"
+                    wrapGApp "${linkPath}"
+                done
+        fi
+    fi
+}
+
+fixupOutputHooks+=(wrapGAppsHook)