diff --git a/pkgs/development/libraries/qt-5/5.15/default.nix b/pkgs/development/libraries/qt-5/5.15/default.nix index 6302d6ab5b31f..735afc3c51464 100644 --- a/pkgs/development/libraries/qt-5/5.15/default.nix +++ b/pkgs/development/libraries/qt-5/5.15/default.nix @@ -10,6 +10,7 @@ generateSplicesForMkScope, lib, stdenv, + callPackages, gcc14Stdenv, fetchurl, fetchgit, @@ -297,6 +298,7 @@ let wrapQtAppsHook = callPackage ( { + wrapQtAppsHook, makeBinaryWrapper, qtbase, qtwayland, @@ -308,6 +310,9 @@ let makeBinaryWrapper ] ++ lib.optional stdenv.hostPlatform.isLinux qtwayland.dev; + passthru.tests = callPackages ../../qt-6/tests/wrap-qt-apps-hook.nix { + inherit qtbase wrapQtAppsHook; + }; meta.license = lib.licenses.mit; } ../hooks/wrap-qt-apps-hook.sh ) { }; diff --git a/pkgs/development/libraries/qt-5/hooks/wrap-qt-apps-hook.sh b/pkgs/development/libraries/qt-5/hooks/wrap-qt-apps-hook.sh index 6f7cdac0c9418..0bbdeea1796a4 100644 --- a/pkgs/development/libraries/qt-5/hooks/wrap-qt-apps-hook.sh +++ b/pkgs/development/libraries/qt-5/hooks/wrap-qt-apps-hook.sh @@ -3,8 +3,11 @@ __nix_wrapQtAppsHook=1 # wrap only once per output declare -a qtWrapperDoneForOuputs -# Inherit arguments given in mkDerivation -qtWrapperArgs=( ${qtWrapperArgs-} ) +# Normalize qtWrapperArgs to an array +declare -a _qtWrapperArgsTmp +concatTo _qtWrapperArgsTmp qtWrapperArgs +qtWrapperArgs=("${_qtWrapperArgsTmp[@]}") +unset _qtWrapperArgsTmp qtHostPathSeen=() diff --git a/pkgs/development/libraries/qt-6/default.nix b/pkgs/development/libraries/qt-6/default.nix index dc7233f11a8c4..b8a7afb5496c4 100644 --- a/pkgs/development/libraries/qt-6/default.nix +++ b/pkgs/development/libraries/qt-6/default.nix @@ -2,6 +2,7 @@ newScope, lib, stdenv, + callPackages, generateSplicesForMkScope, makeScopeWithSplicing', fetchurl, @@ -124,6 +125,7 @@ let wrapQtAppsHook = callPackage ( { + wrapQtAppsHook, makeBinaryWrapper, qtwayland, qtbase, @@ -134,6 +136,9 @@ let depsTargetTargetPropagated = [ (onlyPluginsAndQml qtbase) ]; + passthru.tests = callPackages ./tests/wrap-qt-apps-hook.nix { + inherit qtbase wrapQtAppsHook; + }; meta.license = lib.licenses.mit; } ./hooks/wrap-qt-apps-hook.sh ) { }; diff --git a/pkgs/development/libraries/qt-6/hooks/wrap-qt-apps-hook.sh b/pkgs/development/libraries/qt-6/hooks/wrap-qt-apps-hook.sh index 18056b9ddf3bc..248d00b3dda2b 100644 --- a/pkgs/development/libraries/qt-6/hooks/wrap-qt-apps-hook.sh +++ b/pkgs/development/libraries/qt-6/hooks/wrap-qt-apps-hook.sh @@ -3,8 +3,11 @@ if [[ -z "${__nix_wrapQtAppsHook-}" ]]; then # wrap only once per output declare -a qtWrapperDoneForOuputs - # Inherit arguments given in mkDerivation - qtWrapperArgs=(${qtWrapperArgs-}) + # Normalize qtWrapperArgs to an array + declare -a _qtWrapperArgsTmp + concatTo _qtWrapperArgsTmp qtWrapperArgs + qtWrapperArgs=("${_qtWrapperArgsTmp[@]}") + unset _qtWrapperArgsTmp qtHostPathSeen=() diff --git a/pkgs/development/libraries/qt-6/tests/wrap-qt-apps-hook.nix b/pkgs/development/libraries/qt-6/tests/wrap-qt-apps-hook.nix new file mode 100644 index 0000000000000..e9010031e879c --- /dev/null +++ b/pkgs/development/libraries/qt-6/tests/wrap-qt-apps-hook.nix @@ -0,0 +1,174 @@ +{ + lib, + qtbase, + runCommandLocal, + stdenv, + wrapQtAppsHook, +}: +let + testProgram = stdenv.mkDerivation { + pname = "qt-wrapper-test-program"; + version = "0.0.1"; + __structuredAttrs = true; + + dontUnpack = true; + + src = /* c */ '' + #include + #include + #include + + int main(void) { + const char *wrapperTest = getenv("WRAPPER_TEST"); + if (!wrapperTest || strcmp(wrapperTest, "expected") != 0) { + fprintf(stderr, + "expected WRAPPER_TEST=expected, got=%s\n", + wrapperTest); + return 1; + } + + const char *plugin_path = getenv("QT_PLUGIN_PATH"); + if (!plugin_path) { + fprintf(stderr, "QT_PLUGIN_PATH not set\n"); + return 1; + } + + if (!strstr(plugin_path, + "${qtbase}/${qtbase.qtPluginPrefix}")) { + fprintf(stderr, + "QT_PLUGIN_PATH missing Qt plugin dir:\n%s\n", + plugin_path); + return 1; + } + + return 0; + } + ''; + + buildPhase = '' + "$CC" -x c - -o test <<<"$src" + ''; + + installPhase = '' + mkdir -p $out/bin + install -m755 test $out/bin/test + ''; + + meta.mainProgram = "test"; + }; + + # Test that wrapQtAppsHook produces the expected qtWrapperArgs bash array + checkWrapperArgsArray = + { + name, + qtWrapperArgs, + }: + runCommandLocal "${name}-check-qtWrapperArgs" + { + inherit qtWrapperArgs; + buildInputs = [ qtbase ]; + nativeBuildInputs = [ wrapQtAppsHook ]; + __structuredAttrs = true; + } + '' + ${lib.toShellVars { + # The resulting array should start with the exact user-defined args: + userDefinedArgs = qtWrapperArgs; + + # The hook's setup should also add internal flags, including: + internalArgs = [ + "QT_PLUGIN_PATH" + "${qtbase}/${qtbase.qtPluginPrefix}" + ]; + }} + + # The hook should have run its setup already, converting qtWrapperArgs + # to an array and appending internal flags. + type=$(declare -p qtWrapperArgs) + [[ "$type" == "declare -"*a* ]] || { + echo "Expected qtWrapperArgs to be an array, found: ''${type%%=*}" >&2 + exit 1 + } + + # The start of the array should equal the user-defined args + for i in "''${!userDefinedArgs[@]}"; do + actual="''${qtWrapperArgs[$i]}" + expected="''${userDefinedArgs[$i]}" + [ "$expected" = "$actual" ] || { + echo "qtWrapperArgs[$i]: expected '$expected', found '$actual'" >&2 + exit 1 + } + done + + # Internal args should be appended to the array + internalSlice=("''${qtWrapperArgs[@]:''${#userDefinedArgs[@]}}") + for expected in "''${internalArgs[@]}"; do + found= + for actual in "''${internalSlice[@]}"; do + if [ "$expected" = "$actual" ]; then + found=1 + break + fi + done + + [ -n "$found" ] || { + echo "Internal arg '$expected' not found in internal qtWrapperArgs" >&2 + exit 1 + } + done + + # The hook should've defined the wrapQtAppsHook function + [ "$(type -t wrapQtAppsHook)" = function ] || { + echo "wrapQtAppsHook is not declared as a function" >&2 + exit 1 + } + + touch "$out" + ''; +in + +lib.fix (self: { + + simple = checkWrapperArgsArray { + name = "simple"; + qtWrapperArgs = [ + "--chdir" + "/foo" + ]; + }; + + simple-no-structuredAttrs = self.simple.overrideAttrs (prevAttrs: { + name = prevAttrs.name + "-no-structuredAttrs"; + __structuredAttrs = false; + }); + + # Integration test: assert program is wrapped with the expected environment + runtime = + runCommandLocal "simple-wrapper-runtime" + { + qtWrapperArgs = [ + "--set" + "WRAPPER_TEST" + "expected" + ]; + + buildInputs = [ qtbase ]; + nativeBuildInputs = [ wrapQtAppsHook ]; + __structuredAttrs = true; + } + '' + # Install the test program + mkdir -p "$out/bin" + cp ${lib.getExe testProgram} "$out/bin/test" + + # Wrap and run the test + wrapQtAppsHook + "$out/bin/test" + ''; + + runtime-no-structuredAttrs = self.runtime.overrideAttrs (prevAttrs: { + name = prevAttrs.name + "-no-structuredAttrs"; + __structuredAttrs = false; + }); + +}) diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix index 63e8e2b32aaef..d02f45fb48845 100644 --- a/pkgs/test/default.nix +++ b/pkgs/test/default.nix @@ -254,6 +254,16 @@ in ) pkgs.arrayUtilities ); + # Accumulate all passthru.tests from qt5 into a single attribute set. + qt5 = recurseIntoAttrs { + wrapQtAppsHook = recurseIntoAttrs pkgs.qt5.wrapQtAppsHook.passthru.tests; + }; + + # Accumulate all passthru.tests from qt6 into a single attribute set. + qt6 = recurseIntoAttrs { + wrapQtAppsHook = recurseIntoAttrs pkgs.qt6.wrapQtAppsHook.passthru.tests; + }; + srcOnly = callPackage ../build-support/src-only/tests.nix { }; systemd = callPackage ./systemd { };