From e64ef4b13710934154312e52349f882a82a2002e Mon Sep 17 00:00:00 2001 From: Stefan Frijters Date: Mon, 8 Jun 2026 16:12:55 +0200 Subject: [PATCH 1/3] wrapGAppsHook: make more bash variables local --- .../setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh b/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh index ea3e81bfdf27..92b7a5eee3e7 100644 --- a/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh +++ b/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh @@ -51,6 +51,8 @@ wrapGAppsHook() { wrapGAppsHookHasRunForOutput["$output"]=1 if [[ -z "${dontWrapGApps:-}" ]]; then + local targetDirsThatExist targetDirsRealPath targetDirs targetDir + targetDirsThatExist=() targetDirsRealPath=() @@ -71,6 +73,7 @@ wrapGAppsHook() { # 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 + local linkPathReal targetPath if [[ ${#targetDirsThatExist[@]} -ne 0 ]]; then find "${targetDirsThatExist[@]}" -type l -xtype f -executable -print0 | while IFS= read -r -d '' linkPath; do From e858e80fb18c50f8cb8eaf750bbc2a2075207103 Mon Sep 17 00:00:00 2001 From: Stefan Frijters Date: Tue, 9 Jun 2026 10:05:48 +0200 Subject: [PATCH 2/3] wrapGAppsHook: only wrap $outBin by default --- doc/languages-frameworks/gnome.section.md | 2 +- .../wrap-gapps-hook/wrap-gapps-hook.sh | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/doc/languages-frameworks/gnome.section.md b/doc/languages-frameworks/gnome.section.md index ecfda62b5700..7e9130218ca6 100644 --- a/doc/languages-frameworks/gnome.section.md +++ b/doc/languages-frameworks/gnome.section.md @@ -105,7 +105,7 @@ Given the requirements above, the package expression would become messy quickly: } ``` -Fortunately, we have a [family of hooks]{#ssec-gnome-hooks-wrapgappshook} that automate this. They work in conjunction with other setup hooks that populate environment variables, and will then wrap all executables in `bin` and `libexec` directories using said variables. +Fortunately, we have a [family of hooks]{#ssec-gnome-hooks-wrapgappshook} that automate this. They work in conjunction with other setup hooks that populate environment variables, and will then wrap all executables in `bin` and `libexec` directories using said variables. If a package has multiple outputs, these hooks will work on `outputBin` by default, or on the outputs listed in `wrapGAppsInOutputs` if set. - [`wrapGAppsHook3`]{#ssec-gnome-hooks-wrapgappshook3} for GTK 3 apps. For convenience, it also adds `dconf.lib` for a GIO module implementing a GSettings backend using `dconf`, `gtk3` for GSettings schemas, and `librsvg` for GdkPixbuf loader to the closure. - [`wrapGAppsHook4`]{#ssec-gnome-hooks-wrapgappshook4} for GTK 4 apps. Same as `wrapGAppsHook3` but replaces `gtk3` with `gtk4`. diff --git a/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh b/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh index 92b7a5eee3e7..ef2c02dff02e 100644 --- a/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh +++ b/pkgs/build-support/setup-hooks/wrap-gapps-hook/wrap-gapps-hook.sh @@ -42,13 +42,32 @@ wrapGApp() { wrapProgram "$program" "${gappsWrapperArgs[@]}" "$@" } +_wrapGAppsHookMayRunForOutput() { + local -r output="$1" + if [[ -v wrapGAppsInOutputs ]]; then + local allowedOutput + # Support both structuredAttrs on and off + local -a allowedOutputs + concatTo allowedOutputs wrapGAppsInOutputs + for allowedOutput in "${allowedOutputs[@]}"; do + [ "$allowedOutput" = "$output" ] && return 0 + done + else + [ "$outputBin" = "$output" ] && return 0 + fi + return 1 + } + declare -gA wrapGAppsHookHasRunForOutput # Note: $gappsWrapperArgs still gets defined even if ${dontWrapGApps-} is set. +# $output is brought into scope via fixupPhase() in pkgs/stdenv/generic/setup.sh wrapGAppsHook() { # guard against running multiple times for the same output (e.g. due to propagation) [ "${wrapGAppsHookHasRunForOutput["$output"]:-}" = 1 ] && return 0 wrapGAppsHookHasRunForOutput["$output"]=1 + # guard against running for outputs we don't want to run it for + _wrapGAppsHookMayRunForOutput "$output" || return 0 if [[ -z "${dontWrapGApps:-}" ]]; then local targetDirsThatExist targetDirsRealPath targetDirs targetDir From 5767290bcd2e2c8d955006718324f26b8f688be5 Mon Sep 17 00:00:00 2001 From: Stefan Frijters Date: Tue, 9 Jun 2026 10:06:33 +0200 Subject: [PATCH 3/3] wrapGAppsHook: add tests for multiple outputs --- .../setup-hooks/wrap-gapps-hook/default.nix | 189 +++++++++++++++++- 1 file changed, 188 insertions(+), 1 deletion(-) diff --git a/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix b/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix index 73c2782cfe07..7212578e7405 100644 --- a/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix +++ b/pkgs/build-support/setup-hooks/wrap-gapps-hook/default.nix @@ -76,7 +76,136 @@ makeSetupHook { ]; }; - # The wrapper for executable files should add path to dconf GIO module. + # Simple derivation containing a program and a daemon, but split over multiple outputs. + basic-multiple-outputs = stdenv.mkDerivation { + name = "basic-multiple-outputs"; + + src = sample-project; + + outputs = [ + "out" + "lib" + ]; + + strictDeps = true; + nativeBuildInputs = [ wrapGAppsHook3 ]; + + installFlags = [ + "bin-foo" + "libexec-bar" + ]; + + postInstall = '' + mkdir -p $lib + mv $out/libexec $lib + # Wrapper will want to append this to XDG_DATA_DIRS, but should not cause a cyclic dependency; + # i.e. only "out" will be wrapped. + mkdir -p $out/share + ''; + }; + + # Simple derivation containing a program and a daemon, but using a non-default output + # Executables in "bin" should be handled correctly automatically + basic-bin-output = stdenv.mkDerivation { + name = "basic-bin-output"; + + src = sample-project; + + outputs = [ + "bin" + "dev" + "lib" + "out" + ]; + + strictDeps = true; + nativeBuildInputs = [ wrapGAppsHook3 ]; + + installFlags = [ + "bin-foo" + "libexec-bar" + ]; + + postInstall = '' + mkdir -p $lib + mv $out/libexec $lib + mkdir -p $bin + mv $out/bin $bin + ''; + }; + + # Simple derivation containing a program and a daemon, but using a non-default output + basic-other-outputs = stdenv.mkDerivation { + name = "basic-other-outputs"; + + src = sample-project; + + outputs = [ + "dev" + "lib" + "out" + ]; + + strictDeps = true; + nativeBuildInputs = [ wrapGAppsHook3 ]; + + installFlags = [ + "bin-foo" + "libexec-bar" + ]; + + wrapGAppsInOutputs = [ + "dev" + "lib" + ]; + + postInstall = '' + mkdir -p $lib + mv $out/libexec $lib + mkdir -p $dev + mv $out/bin $dev + ''; + }; + + # Simple derivation containing a program and a daemon, but using non-default outputs + # that are explicitly referenced via wrapGAppsInOutputs + basic-other-outputs-structuredattrs = stdenv.mkDerivation { + name = "basic-other-outputs-structuredAttrs"; + + __structuredAttrs = true; + + src = sample-project; + + outputs = [ + "dev" + "lib" + "out" + ]; + + strictDeps = true; + nativeBuildInputs = [ wrapGAppsHook3 ]; + + installFlags = [ + "bin-foo" + "libexec-bar" + ]; + + wrapGAppsInOutputs = [ + "dev" + "lib" + ]; + + postInstall = '' + mkdir -p $lib + mv $out/libexec $lib + mkdir -p $dev + mv $out/bin $dev + ''; + }; + + # Simple derivation containing a program and a daemon, but using non-default outputs + # that are explicitly referenced via wrapGAppsInOutputs, while structuredAttrs are enabled + # so that it is a proper array. basic-contains-dconf = let tested = basic; @@ -92,6 +221,64 @@ makeSetupHook { '' ); + # The wrapper for executable files should add path to dconf GIO module. + basic-multiple-outputs-contains-dconf = + let + tested = basic-multiple-outputs; + in + testLib.runTest "basic-multiple-outputs-contains-dconf" ( + testLib.skip stdenv.hostPlatform.isDarwin '' + ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GIO_EXTRA_MODULES" + "${dconf.lib}/lib/gio/modules" + } + '' + ); + + # The wrapper for executable files should add path to dconf GIO module. + basic-bin-output-contains-dconf = + let + tested = basic-bin-output; + in + testLib.runTest "basic-bin-output-contains-dconf" ( + testLib.skip stdenv.hostPlatform.isDarwin '' + ${expectSomeLineContainingYInFileXToMentionZ "${tested}/bin/foo" "GIO_EXTRA_MODULES" + "${dconf.lib}/lib/gio/modules" + } + '' + ); + + # The wrapper for executable files should add path to dconf GIO module. + basic-other-outputs-contains-dconf = + let + tested = basic-other-outputs; + in + testLib.runTest "basic-other-outputs-contains-dconf" ( + testLib.skip stdenv.hostPlatform.isDarwin '' + ${expectSomeLineContainingYInFileXToMentionZ "${tested.dev}/bin/foo" "GIO_EXTRA_MODULES" + "${dconf.lib}/lib/gio/modules" + } + ${expectSomeLineContainingYInFileXToMentionZ "${tested.lib}/libexec/bar" "GIO_EXTRA_MODULES" + "${dconf.lib}/lib/gio/modules" + } + '' + ); + + # The wrapper for executable files should add path to dconf GIO module. + basic-other-outputs-contains-dconf-structuredattrs = + let + tested = basic-other-outputs-structuredattrs; + in + testLib.runTest "basic-other-outputs-structuredattrs-contains-dconf" ( + testLib.skip stdenv.hostPlatform.isDarwin '' + ${expectSomeLineContainingYInFileXToMentionZ "${tested.dev}/bin/foo" "GIO_EXTRA_MODULES" + "${dconf.lib}/lib/gio/modules" + } + ${expectSomeLineContainingYInFileXToMentionZ "${tested.lib}/libexec/bar" "GIO_EXTRA_MODULES" + "${dconf.lib}/lib/gio/modules" + } + '' + ); + basic-contains-gdk-pixbuf = let tested = basic;