diff --git a/.ci/compile.sh b/.ci/compile.sh index f20432893..72a017f33 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -352,6 +352,62 @@ if [[ $MAKE_PACKAGE ]]; then exit 1 fi echo "Installer content is clean" + + # Fail the build if the installer is missing a Qt runtime the applications + # link against. The DLLs reach the package by globbing every *.dll out of + # the build output directories, so a module the client links statically + # (Qt6Multimedia.dll, Qt6QuickWidgets.dll) can be left out without anything + # else noticing, and the only symptom is a client that refuses to start + # after an update. required-qt-runtime.txt is written by the top level + # CMakeLists.txt from the same module list the targets are built against. + if [[ ! -f required-qt-runtime.txt ]]; then + echo "::error file=$0::required-qt-runtime.txt not found in the build dir, cannot verify the Qt runtime" + exit 1 + fi + # 7-Zip writes CRLF on Windows, so the \r has to come off before any + # whole-line comparison below. Normalize separators to /, and strip the + # install-root prefixes an NSIS listing may carry, so the entries can be + # compared as exact relative paths. -xF keeps a '.' in a DLL name from + # being a wildcard; -i because that is how the installed tree behaves - + # the uninstaller clears both $INSTDIR\plugins and $INSTDIR\Plugins. + # The archive side and the manifest side have to agree on line endings. + # CMake writes required-qt-runtime.txt with the platform's line ending and + # 7-Zip writes CRLF on Windows, so a trailing \r survives on $entry and + # defeats the whole-line match - every entry then reads as missing even + # though the listing holds it verbatim. + # [$] rather than \$ so the literal dollar is a bracket expression and the + # linter does not read these as unexpanded shell variables (SC2016). + installer_paths="$("$seven_zip" l -slt "$package" | tr -d '\r' | + sed -n 's/^Path = //p' | + sed -e 's|\\|/|g' -e 's|^[$]INSTDIR/||' -e 's|^[$]OUTDIR/||' -e 's|^[$]PLUGINSDIR/||' -e 's|^\./||' -e 's|^/||')" + manifest="$(tr -d '\r' " DESTINATION ./) + endif() + endforeach() + install( DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/" DESTINATION ./ @@ -729,11 +747,15 @@ if(WIN32) PATTERN "tls/qschannelbackend.dll" ) + # The Windows install has no Resources directory: the .qm files go to + # ${COCKATRICE_WIN32_QM_INSTALL_DIR} and main.cpp looks for them at + # applicationDirPath() + "/translations". The macOS bundle above keeps + # Resources/translations, which is correct for it. install( CODE " file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] Plugins = Plugins -Translations = Resources/translations +Translations = translations Data = Resources\") " COMPONENT Runtime diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp index c84a46528..bac270580 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp @@ -265,8 +265,16 @@ void DlgUpdate::downloadSuccessful(const QUrl &filepath) // NSIS needs the /D= argument to be an UNQUOTED string, even if it contains spaces. Qt likes to quote arguments if // they contain spaces, so we use the windows exclusive QProcess::setNativeArguments in the only case where this is // relevant, which preserves the argument unquoted. + // + // /PID= tells the installer which process to wait for before it replaces anything. We launch the installer first + // and only quit afterwards, so it starts while this process is still alive and still has every Qt runtime DLL + // mapped, and a mapped DLL cannot be deleted. Without the handle the installer can only poll for an image name and + // guess, and a wrong guess is what leaves the update writing new executables next to runtime files of the previous + // version, which fails on the next start with "The procedure entry point ... could not be located in the dynamic + // link library + // ...Qt6QuickWidgets.dll". /D= has to stay the last parameter, which is why it is last here too. #ifdef Q_OS_WIN - process.setNativeArguments(QString("/R /D=%1").arg(appDir)); + process.setNativeArguments(QString("/R /PID=%1 /D=%2").arg(QCoreApplication::applicationPid()).arg(appDir)); #else // Linux/macOS: normal argument passing (not relevant since they update differently.) process.setArguments({"/R", QString("/D=%1").arg(appDir)});