diff --git a/CMakeLists.txt b/CMakeLists.txt index b977098..ecc8913 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,27 +70,48 @@ endif() install(TARGETS apart-controller RUNTIME DESTINATION .) if(WIN32) - # Run windeployqt at install time to gather Qt DLLs + plugins next to the exe. - find_program(WINDEPLOYQT_EXECUTABLE windeployqt - HINTS "${Qt6_DIR}/../../../bin" "${Qt6_DIR}/../../../../bin") - if(WINDEPLOYQT_EXECUTABLE) - install(CODE " - execute_process(COMMAND \"${WINDEPLOYQT_EXECUTABLE}\" - --release --no-translations --compiler-runtime - --dir \"\${CMAKE_INSTALL_PREFIX}\" - \"\${CMAKE_INSTALL_PREFIX}/apart-controller.exe\") - ") - else() - message(WARNING "windeployqt not found - Qt DLLs will not be auto-deployed.") - endif() - - # MinGW runtime DLLs (windeployqt's --compiler-runtime often skips these). get_filename_component(_mingw_bin "${CMAKE_CXX_COMPILER}" DIRECTORY) - foreach(_dll libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll) - if(EXISTS "${_mingw_bin}/${_dll}") - install(FILES "${_mingw_bin}/${_dll}" DESTINATION .) + find_program(WINDEPLOYQT_EXECUTABLE windeployqt + HINTS "${_mingw_bin}" "${Qt6_DIR}/../../../bin") + + # Pass config-time paths into the install-time script below. + install(CODE "set(_WINDEPLOYQT \"${WINDEPLOYQT_EXECUTABLE}\")") + install(CODE "set(_MINGW_BIN \"${_mingw_bin}\")") + + install(CODE [[ + if(POLICY CMP0207) + cmake_policy(SET CMP0207 NEW) endif() - endforeach() + + # 1) Resolve the exe's full transitive DLL closure from the mingw bin + # dir. This MUST run before windeployqt: at this point the package + # contains only the .exe, so each dependency (Qt6Core.dll, libpng, + # zlib, freetype, harfbuzz, pcre2, zstd, brotli, icu, libgcc/ + # libstdc++/winpthread, ...) resolves to exactly one path. Running + # after windeployqt would find duplicates (staging vs. mingw) and + # abort with "Multiple conflicting paths". System DLLs are excluded. + file(GET_RUNTIME_DEPENDENCIES + EXECUTABLES "${CMAKE_INSTALL_PREFIX}/apart-controller.exe" + RESOLVED_DEPENDENCIES_VAR _resolved + UNRESOLVED_DEPENDENCIES_VAR _unresolved + DIRECTORIES "${_MINGW_BIN}" + PRE_EXCLUDE_REGEXES "api-ms-win-.*" "ext-ms-.*" + POST_EXCLUDE_REGEXES ".*[Ss]ystem32.*" ".*[Ss]ysWOW64.*") + foreach(_dll ${_resolved}) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}" + TYPE SHARED_LIBRARY FOLLOW_SYMLINK_CHAIN FILES "${_dll}") + endforeach() + + # 2) windeployqt now adds the runtime-loaded Qt *plugins* + # (platforms/qwindows.dll, styles, imageformats) plus any helper + # DLLs those plugins need. It is fine for it to re-copy Qt DLLs. + if(_WINDEPLOYQT) + execute_process(COMMAND "${_WINDEPLOYQT}" + --release --no-translations --no-system-d3d-compiler + --dir "${CMAKE_INSTALL_PREFIX}" + "${CMAKE_INSTALL_PREFIX}/apart-controller.exe") + endif() + ]]) endif() set(CPACK_PACKAGE_NAME "Apart Concept 1 Controller")