How to embed custom resources into the installer used for online package updates (QtIFW)
-
Hello everyone,
I am working with the Qt Installer Framework on Windows and I am facing an issue I cannot fully understand or solve. I hope someone here can clarify whether what I want to achieve is possible, or if I am approaching the problem incorrectly.
Context / What I am doing
- I developed a C++ Qt application for Windows.
- I use QtIFW both for installation and for updating via the Maintenance Tool.
- During the installation phase only, I need to run a small helper executable (a custom tool).
- I decided to embed this executable into the installer using the
-r <file.qrc>option ofbinarycreator.- When building the offline or hybrid installer, the resource is correctly included.
- My controller script successfully extracts the embedded file using the undocumented syntax
":/<filename>". - The component script can execute the tool, and I can see its effects.
- When launching the installer from the command line, debug logs confirm that the embedded file is extracted properly.
Goal / Desired behavior
I need the same embedded resource to also be available in the installer that gets deployed when the user updates via the online repository, following the procedure described in:
https://doc.qt.io/qtinstallerframework/ifw-updates.html (see: “Promoting Updates for the Maintenance Tool”).More precisely:
after runningrepogen, the Maintenance Tool downloads a new installer (the replacement for the existing one).I want this new installer to also contain the same embedded resource—just like the offline/hybrid installer built with
-r.Problem
I cannot find a way to embed the custom resource into the installer that is produced for online updates.
When generating
update.rcc(the resource file used for online updates), I run:binarycreator -c config/config.xml -p packages -rccHowever, if
binarycreatoris invoked with-rcc, all-r <resource.qrc>arguments are ignored.
This is an hardcoded behavior (I checked the sources ofbinarycreator) —binarycreatordoes not merge extra resources intoupdate.rcc.As far as I can see, this makes it impossible to embed arbitrary custom executables into the online-update installer, unless QtIFW itself is modified.
Questions
- Is there any supported mechanism to include additional resources inside the installer (the one deployed through online updates)?
- Is there perhaps a different workflow for embedding custom executables into the update installer?
- Or is it correct that, at the moment,
binarycreatorsimply ignores-rwhen-rccis used, making this impossible? - If so, is there a recommended workaround?
Notes
I am posting (her below) my CMake setup (large, but provided for completeness). Some sections—such as the custom launcher—are unrelated to the issue.
Additional thought
It seems logical that
update.rccshould contain my custom resource, but becausebinarycreator -rccdiscards all-roptions, this cannot be achieved unless QtIFW changes this behavior.
If anyone knows whether there is an internal option, undocumented parameter, or alternative approach, I would be grateful.Thanks in advance!
— Lorenzo
My CMakeLists.txt
############################################################################## Copyright (C) 2021 by Lorenzo Buzzi (lorenzo@buzzi.pro) # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see <https://www.gnu.org/licenses/>. # ############################################################################## # Windows installer creation (with Qt Installer Framework) #include(CMakePrintHelpers) # QtIFW resources directory (build) set(QTIFW_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources") file(MAKE_DIRECTORY "${QTIFW_RESOURCES_DIR}") # Extra tools: SetAppUserModelId add_executable(setappusermodelid tools/setappusermodelid.cpp) set_target_properties(setappusermodelid PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS NO RUNTIME_OUTPUT_NAME "SetAppUserModelId" RUNTIME_OUTPUT_DIRECTORY "${QTIFW_RESOURCES_DIR}") target_link_libraries(setappusermodelid PRIVATE ole32 shlwapi propsys) # Once the target 'setappusermodelid' has been built, copy the .qrc set(SETAPPUSERMODELID_QRC "${QTIFW_RESOURCES_DIR}/setappusermodelid.qrc") add_custom_command(TARGET setappusermodelid POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/tools/setappusermodelid.qrc" "${SETAPPUSERMODELID_QRC}" COMMENT "Copying setappusermodelid.qrc to ${QTIFW_RESOURCES_DIR}" USES_TERMINAL) # The installer add_custom_target(installer COMMENT "Setting up the tree for the Qt Installer Framework") # External tools get_target_property(QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION) get_filename_component(_QT_BIN_DIR ${QMAKE_EXECUTABLE} DIRECTORY) # Look for 'windeployqt' find_program(WINDEPLOYQT_EXECUTABLE NAMES windeployqt HINTS "${_QT_BIN_DIR}" REQUIRED DOC "The Windows deployment tool") # Look for 'binarycreator' find_program(QTIFW_BINARYCREATOR_EXECUTABLE NAMES binarycreator HINTS "C:/Qt/Tools/QtInstallerFramework/4.10" PATH_SUFFIXES "bin" REQUIRED DOC "Qt Installer Framework binary creator") # Look for 'repogen' find_program(QTIFW_REPOGEN_EXECUTABLE NAMES repogen HINTS "C:/Qt/Tools/QtInstallerFramework/4.10" PATH_SUFFIXES "bin" REQUIRED DOC "Qt Installer Framework repository generator") # Look for 'installerbase' find_program(QTIFW_INSTALLERBASE_EXECUTABLE NAMES installerbase HINTS "C:/Qt/Tools/QtInstallerFramework/4.10" PATH_SUFFIXES "bin" REQUIRED DOC "Qt Installer Framework base binary") mark_as_advanced(WINDEPLOYQT_EXECUTABLE QTIFW_BINARYCREATOR_EXECUTABLE QTIFW_REPOGEN_EXECUTABLE QTIFW_INSTALLERBASE_EXECUTABLE) # Convenience path variables set(WIN_DEPLOY_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/packages/pro.buzzi.lbchronorace/data") set(WIN_DEPLOY_SAMPLES_DIR "${CMAKE_CURRENT_BINARY_DIR}/packages/pro.buzzi.lbchronorace.samples/data") set(WIN_DEPLOY_MAINTENANCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/packages/org.qtproject.ifw.maintenancetool/data") set(WIN_DEPLOY_DIRS "config" "packages") set(WIN_REPOSITORY_DIR "${CMAKE_BINARY_DIR}/repository") # Cleanup foreach(WIN_DEPLOY_DIR ${WIN_DEPLOY_DIRS}) file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/${WIN_DEPLOY_DIR}") endforeach() file(REMOVE_RECURSE "${WIN_REPOSITORY_DIR}") # Create the basic tree file(COPY ${WIN_DEPLOY_DIRS} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" PATTERN "*.in" EXCLUDE PATTERN "*.ts" EXCLUDE) # Copy the XMLs: replacing the version and date placeholders but preserve other ones set(at "@" CACHE STRING "QTIFW @ placeholder") file(GLOB_RECURSE WIN_DEPLOY_CONFIG_XML RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.xml.in") foreach(IN_FILE ${WIN_DEPLOY_CONFIG_XML}) cmake_path(REMOVE_EXTENSION IN_FILE LAST_ONLY OUTPUT_VARIABLE OUT_FILE) configure_file(${IN_FILE} ${OUT_FILE} NEWLINE_STYLE WIN32) endforeach() unset(at CACHE) # Installer images and icons file(COPY "${CMAKE_SOURCE_DIR}/icons/installer.ico" "${CMAKE_SOURCE_DIR}/icons/LBChronoRace.ico" "${CMAKE_SOURCE_DIR}/icons/LBChronoRace-32.png" "${CMAKE_SOURCE_DIR}/icons/LBChronoRace-64.png" "${CMAKE_SOURCE_DIR}/icons/LBChronoRace-128.png" "${CMAKE_SOURCE_DIR}/icons/LBChronoRace-256.png" "${CMAKE_SOURCE_DIR}/images/lbchronorace-preview.png" "${CMAKE_SOURCE_DIR}/images/lbchronorace-sample.png" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/config") # Installer translations set(WIN_DEPLOY_TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/packages/pro.buzzi.lbchronorace/meta/en.ts" "${CMAKE_CURRENT_SOURCE_DIR}/packages/pro.buzzi.lbchronorace/meta/it.ts") qt_add_translations(installer TS_FILES ${WIN_DEPLOY_TS_FILES} QM_FILES_OUTPUT_VARIABLE WIN_DEPLOY_QM_FILES) foreach(QM_FILE ${WIN_DEPLOY_QM_FILES}) get_filename_component(QM_NAME "${QM_FILE}" NAME) set(QM_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/packages/pro.buzzi.lbchronorace/meta/${QM_NAME}") add_custom_command( OUTPUT ${QM_OUTPUT} COMMAND ${CMAKE_COMMAND} -E copy "${QM_FILE}" "${QM_OUTPUT}" DEPENDS ${QM_FILE} COMMENT "Copying translation file: ${QM_NAME}") list(APPEND INSTALLED_QM_FILES ${QM_OUTPUT}) endforeach() add_custom_target(install_qms DEPENDS ${INSTALLED_QM_FILES}) add_dependencies(installer install_qms) # Installer license files (moving to Windows newline style) file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/packages/pro.buzzi.lbchronorace/meta/license.txt" INPUT "${CMAKE_SOURCE_DIR}/licenses/gpl-3.0.txt" NEWLINE_STYLE WIN32) file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/packages/pro.buzzi.lbchronorace/meta/license_it.txt" INPUT "${CMAKE_SOURCE_DIR}/licenses/gpl-3.0_it.txt" NEWLINE_STYLE WIN32) # Application license file (moving to Windows newline style) file(GENERATE OUTPUT "${WIN_DEPLOY_TARGET_DIR}/GPLv3.txt" INPUT "${CMAKE_SOURCE_DIR}/licenses/gpl-3.0.txt" NEWLINE_STYLE WIN32) # Application icons file(GLOB WIN_DEPLOY_ICONS LIST_DIRECTORIES false "${CMAKE_SOURCE_DIR}/icons/*.ico") file(COPY ${WIN_DEPLOY_ICONS} DESTINATION ${WIN_DEPLOY_TARGET_DIR}) # Data samples file(MAKE_DIRECTORY "${WIN_DEPLOY_SAMPLES_DIR}/samples") file(GLOB_RECURSE WIN_DEPLOY_SAMPLES_CONTENTS LIST_DIRECTORIES true RELATIVE "${CMAKE_SOURCE_DIR}/samples" "${CMAKE_SOURCE_DIR}/samples/*") foreach(SAMPLE_CONTENT ${WIN_DEPLOY_SAMPLES_CONTENTS}) cmake_path(ABSOLUTE_PATH SAMPLE_CONTENT BASE_DIRECTORY "${CMAKE_SOURCE_DIR}/samples" OUTPUT_VARIABLE IN_PATH) cmake_path(ABSOLUTE_PATH SAMPLE_CONTENT BASE_DIRECTORY "${WIN_DEPLOY_SAMPLES_DIR}/samples" OUTPUT_VARIABLE OUT_PATH) if(IS_DIRECTORY ${IN_PATH}) file(MAKE_DIRECTORY ${OUT_PATH}) else() cmake_path(GET SAMPLE_CONTENT EXTENSION LAST_ONLY EXT) if("${EXT}" STREQUAL ".crd") file(COPY_FILE ${IN_PATH} ${OUT_PATH}) else() file(GENERATE OUTPUT ${OUT_PATH} INPUT ${IN_PATH} NEWLINE_STYLE WIN32) endif() endif() endforeach() # Application executable (copy) add_custom_command(TARGET installer PRE_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "$<TARGET_FILE:${LBCHRONORACE_BIN}>" ${WIN_DEPLOY_TARGET_DIR} DEPENDS "$<TARGET_FILE:${LBCHRONORACE_BIN}>" COMMENT "Copy executable into the installer directory" USES_TERMINAL) # Populate the installer tree with the runtime dependencies string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_UPPER) if(CMAKE_BUILD_TYPE_UPPER STREQUAL "DEBUG") set(WINDEPLOYQT_ARGS "--debug") else() set(WINDEPLOYQT_ARGS "--release") endif() file(MAKE_DIRECTORY "${WIN_DEPLOY_MAINTENANCE_DIR}") cmake_path(GET CMAKE_CXX_COMPILER PARENT_PATH _GXX_BIN_DIR) add_custom_command(TARGET installer POST_BUILD COMMAND "${CMAKE_COMMAND}" -E env PATH="${_GXX_BIN_DIR}" ${WINDEPLOYQT_EXECUTABLE} --qmldir "${CMAKE_SOURCE_DIR}" ${WINDEPLOYQT_ARGS} --dir "${WIN_DEPLOY_TARGET_DIR}" --no-compiler-runtime "${WIN_DEPLOY_TARGET_DIR}/$<TARGET_FILE_NAME:${LBCHRONORACE_BIN}>" COMMENT "Deploying ${CMAKE_PROJECT_NAME}..." USES_TERMINAL) # Build the hybrid installer binary set(INSTALLER_BINARY "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}Installer${CMAKE_EXECUTABLE_SUFFIX}") add_custom_command(TARGET installer POST_BUILD COMMAND "${CMAKE_COMMAND}" -E env ${QTIFW_BINARYCREATOR_EXECUTABLE} "--hybrid" -c "config/config.xml" -p "packages" -r "${SETAPPUSERMODELID_QRC}" -v "${INSTALLER_BINARY}" COMMENT "Creating ${CMAKE_PROJECT_NAME} hybrid installer" USES_TERMINAL) # Build the on-line installer binary set(INSTALLER_ONLINE_BINARY "${CMAKE_BINARY_DIR}/installerbase${CMAKE_EXECUTABLE_SUFFIX}") add_custom_command(TARGET installer POST_BUILD COMMAND "${CMAKE_COMMAND}" -E env ${QTIFW_BINARYCREATOR_EXECUTABLE} "--online-only" -t "${QTIFW_INSTALLERBASE_EXECUTABLE}" -c "config/config.xml" -p "packages" -r "${SETAPPUSERMODELID_QRC}" -v "${INSTALLER_ONLINE_BINARY}" COMMENT "Creating ${CMAKE_PROJECT_NAME} on-line installer" USES_TERMINAL) # Copy the on-line installer binary (for the on-line updater) add_custom_command(TARGET installer POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "${INSTALLER_ONLINE_BINARY}" "${WIN_DEPLOY_MAINTENANCE_DIR}" DEPENDS "${INSTALLER_ONLINE_BINARY}" COMMENT "Copy ${INSTALLER_ONLINE_BINARY} into the maintenance tool directory" USES_TERMINAL) # Build the installer's resource file (for on-line update) add_custom_command(TARGET installer POST_BUILD COMMAND "${CMAKE_COMMAND}" -E env ${QTIFW_BINARYCREATOR_EXECUTABLE} -c "config/config.xml" -p "packages" -rcc COMMENT "Creating ${CMAKE_PROJECT_NAME} installer's resource file" USES_TERMINAL) # Copy the installer's resource file add_custom_command(TARGET installer POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "update.rcc" "${WIN_DEPLOY_MAINTENANCE_DIR}" DEPENDS "update.rcc" COMMENT "Copy update.rcc into the maintenance tool directory" USES_TERMINAL) # Build the repository add_custom_command(TARGET installer POST_BUILD COMMAND "${CMAKE_COMMAND}" -E env ${QTIFW_REPOGEN_EXECUTABLE} -p "packages" "${WIN_REPOSITORY_DIR}" COMMENT "Generating QtIFW repository into ${WIN_REPOSITORY_DIR}" USES_TERMINAL) # Build the launcher that set AllUsers configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/launcher.rc.in ${CMAKE_CURRENT_BINARY_DIR}/launcher.rc @ONLY) # Embed the installer in the launcher executable set(INSTALLER_RC "${CMAKE_CURRENT_BINARY_DIR}/installer.rc") file(WRITE ${INSTALLER_RC} "#include \"launcher.hpp\"\n\nIDR_INSTALLER_BIN RCDATA \"${INSTALLER_BINARY}\"\n") # Create the launcher executable set(APP_ICON_RESOURCE_LAUNCHER "${CMAKE_SOURCE_DIR}/icons/LBChronoRace.rc") set(APP_VERSION_RESOURCE_LAUNCHER "${CMAKE_CURRENT_BINARY_DIR}/launcher.rc") if(CMAKE_BUILD_TYPE_UPPER STREQUAL "DEBUG") set_source_files_properties(${APP_VERSION_RESOURCE_LAUNCHER} PROPERTIES COMPILE_DEFINITIONS QT_QML_DEBUG) endif() add_executable(launcher WIN32 EXCLUDE_FROM_ALL "${CMAKE_SOURCE_DIR}/installer/launcher.cpp" ${APP_ICON_RESOURCE_LAUNCHER} ${APP_VERSION_RESOURCE_LAUNCHER} ${INSTALLER_RC}) set_target_properties(launcher PROPERTIES WIN32_EXECUTABLE TRUE RUNTIME_OUTPUT_NAME "${CMAKE_PROJECT_NAME}Installer-${PROJECT_VERSION}u${PROJECT_UPDATE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") target_include_directories(launcher PRIVATE ${CMAKE_BINARY_DIR}) add_dependencies(launcher installer)