[CI] Fail the build when the installer is missing a Qt runtime DLL

The Windows payload is assembled by globbing every *.dll out of the build output
directories, so nothing but luck decides whether a module the client links
statically - Qt6Multimedia.dll, Qt6QuickWidgets.dll - reaches the installer at
all. A module that is left out is invisible until a user installs an update and
the client then refuses to start, with "The code execution cannot proceed because
Qt6Multimedia.dll was not found" or a missing procedure entry point in a
Qt6QuickWidgets.dll carried over from the previous version.

Derive the required list from SERVATRICE_QT_MODULES, COCKATRICE_QT_MODULES and
ORACLE_QT_MODULES, the same module lists the targets are actually built against,
so it cannot drift from what the binaries need, and write it next to the CMake
cache as required-qt-runtime.txt. Add the platform plugin, without which the
client cannot start at all.

Assert it in the existing 7-Zip pass that already rejects build-tree artifacts,
comparing exact relative paths so a same-named file in the wrong directory does
not satisfy a requirement, and normalizing the archive's own separators so it
does not matter which one NSIS chose. Missing manifest, missing entry or missing
7-Zip-independent failures all stop the build rather than warn, which is the
whole point: the previous check already failed hard for artifacts, and a warning
here is what let a broken package ship.
This commit is contained in:
Lukas Brübach 2026-10-01 18:05:30 +02:00
parent 4278e528d0
commit b6fc21c968
2 changed files with 57 additions and 1 deletions

View file

@ -345,13 +345,45 @@ if [[ $MAKE_PACKAGE ]]; then
else
echo "Inspecting $package"
# Fail the build if the installer contains any path left behind by the MSBuild or
# Qt AUTOMOC tooling (build-tree artifacts must live in the build dir, not the install)
# Qt AUTOMOC tooling (build-tree artifacts must live in the build dir, not in the install)
if "$seven_zip" l "$package" |
grep -E "_autogen|\.dir[\\/]|\.tlog|(^|[\\/])x64[\\/]|(^|[\\/])\.qt[\\/]|(^|[\\/])\.qsb[\\/]|(^|[\\/])\.lupdate[\\/]|CMakeFiles"; then
echo "::error file=$0::Installer contains build-tree artifacts"
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
# Normalize the archive's own separators to / so the entries below can be
# compared as exact relative paths, and use -xF so a '.' in a DLL name is
# never a wildcard.
installer_paths="$("$seven_zip" l -slt "$package" | sed -n 's/^Path = //p' | sed 's|\\|/|g')"
missing=""
checked=0
while IFS= read -r entry; do
if [[ -z $entry || $entry == \#* ]]; then
continue
fi
checked=$((checked + 1))
if ! grep -qxF "$entry" <<<"$installer_paths"; then
missing+=" $entry"
fi
done <required-qt-runtime.txt
if [[ -n $missing ]]; then
echo "::error file=$0::Installer is missing required Qt runtime files:$missing"
exit 1
fi
echo "Installer contains the full Qt runtime ($checked files)"
fi
echo "::endgroup::"
fi

View file

@ -326,6 +326,30 @@ elseif(WIN32)
# Configure file with custom definitions for NSIS.
configure_file("${COCKATRICE_CMAKE_PATH}/NSIS.definitions.nsh.in" "${PROJECT_BINARY_DIR}/NSIS.definitions.nsh")
# Every Qt module the configured applications link against, as the DLL names a
# Windows install has to contain, plus the platform plugin without which the
# client cannot start. .ci/compile.sh reads this and fails the build when the
# packaged installer is missing one of them.
#
# The DLLs are deployed by globbing every *.dll out of the build output
# directories, so nothing but luck decides whether a module the client links
# statically - Qt6Multimedia.dll, Qt6QuickWidgets.dll - reaches the installer at
# all. A DLL that is missing from the package only surfaces on a user's machine,
# after an update has already replaced their install, as "The code execution
# cannot proceed because Qt6Multimedia.dll was not found" or as a missing
# procedure entry point in a Qt6QuickWidgets.dll left over from the previous
# version. Assert it while the installer is still being built instead.
set(_required_qt_runtime "")
foreach(_qt_module IN LISTS SERVATRICE_QT_MODULES COCKATRICE_QT_MODULES ORACLE_QT_MODULES)
string(REGEX REPLACE "^Qt6::" "" _qt_component "${_qt_module}")
list(APPEND _required_qt_runtime "Qt6${_qt_component}.dll")
endforeach()
list(APPEND _required_qt_runtime "Plugins/platforms/qwindows.dll")
list(REMOVE_DUPLICATES _required_qt_runtime)
list(SORT _required_qt_runtime)
string(REPLACE ";" "\n" _required_qt_runtime "${_required_qt_runtime}")
file(WRITE "${PROJECT_BINARY_DIR}/required-qt-runtime.txt" "${_required_qt_runtime}\n")
# include vcredist into the package; NSIS will take care of running it
if(VCREDISTRUNTIME_FOUND)
install(FILES "${VCREDISTRUNTIME_FILE}" DESTINATION ./)