diff options
Diffstat (limited to 'src/tools/windeployqt')
-rw-r--r-- | src/tools/windeployqt/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/tools/windeployqt/elfreader.cpp | 417 | ||||
-rw-r--r-- | src/tools/windeployqt/elfreader.h | 151 | ||||
-rw-r--r-- | src/tools/windeployqt/main.cpp | 1240 | ||||
-rw-r--r-- | src/tools/windeployqt/qmlutils.cpp | 3 | ||||
-rw-r--r-- | src/tools/windeployqt/qtmoduleinfo.cpp | 183 | ||||
-rw-r--r-- | src/tools/windeployqt/qtmoduleinfo.h | 51 | ||||
-rw-r--r-- | src/tools/windeployqt/qtplugininfo.cpp | 100 | ||||
-rw-r--r-- | src/tools/windeployqt/qtplugininfo.h | 48 | ||||
-rw-r--r-- | src/tools/windeployqt/utils.cpp | 179 | ||||
-rw-r--r-- | src/tools/windeployqt/utils.h | 31 |
11 files changed, 1261 insertions, 1146 deletions
diff --git a/src/tools/windeployqt/CMakeLists.txt b/src/tools/windeployqt/CMakeLists.txt index 21115427d2..2e50116484 100644 --- a/src/tools/windeployqt/CMakeLists.txt +++ b/src/tools/windeployqt/CMakeLists.txt @@ -12,14 +12,16 @@ qt_internal_add_tool(${target_name} INSTALL_VERSIONED_LINK TARGET_DESCRIPTION "Qt Windows Deployment Tool" SOURCES - elfreader.cpp elfreader.h qmlutils.cpp qmlutils.h + qtmoduleinfo.cpp qtmoduleinfo.h + qtplugininfo.cpp qtplugininfo.h utils.cpp utils.h main.cpp DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII QT_NO_FOREACH + QT_NO_QPAIR LIBRARIES Qt::CorePrivate ) diff --git a/src/tools/windeployqt/elfreader.cpp b/src/tools/windeployqt/elfreader.cpp deleted file mode 100644 index 9ef3b6bfa4..0000000000 --- a/src/tools/windeployqt/elfreader.cpp +++ /dev/null @@ -1,417 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "elfreader.h" - -#include <QDir> - -QT_BEGIN_NAMESPACE - -using namespace Qt::StringLiterals; - -/* This is a copy of the ELF reader contained in Qt Creator (src/libs/utils), - * extended by the dependencies() function to read out the dependencies of a dynamic executable. */ - -quint16 getHalfWord(const unsigned char *&s, const ElfData &context) -{ - quint16 res; - if (context.endian == Elf_ELFDATA2MSB) - res = qFromBigEndian<quint16>(s); - else - res = qFromLittleEndian<quint16>(s); - s += 2; - return res; -} - -quint32 getWord(const unsigned char *&s, const ElfData &context) -{ - quint32 res; - if (context.endian == Elf_ELFDATA2MSB) - res = qFromBigEndian<quint32>(s); - else - res = qFromLittleEndian<quint32>(s); - s += 4; - return res; -} - -quint64 getAddress(const unsigned char *&s, const ElfData &context) -{ - quint64 res; - if (context.elfclass == Elf_ELFCLASS32) { - if (context.endian == Elf_ELFDATA2MSB) - res = qFromBigEndian<quint32>(s); - else - res = qFromLittleEndian<quint32>(s); - s += 4; - } else { - if (context.endian == Elf_ELFDATA2MSB) - res = qFromBigEndian<quint64>(s); - else - res = qFromLittleEndian<quint64>(s); - s += 8; - } - return res; -} - -quint64 getOffset(const unsigned char *&s, const ElfData &context) -{ - return getAddress(s, context); -} - -static void parseSectionHeader(const uchar *s, ElfSectionHeader *sh, const ElfData &context) -{ - sh->index = getWord(s, context); - sh->type = getWord(s, context); - sh->flags = quint32(getOffset(s, context)); - sh->addr = getAddress(s, context); - sh->offset = getOffset(s, context); - sh->size = getOffset(s, context); -} - -static void parseProgramHeader(const uchar *s, ElfProgramHeader *sh, const ElfData &context) -{ - sh->type = getWord(s, context); - sh->offset = getOffset(s, context); - /* p_vaddr = */ getAddress(s, context); - /* p_paddr = */ getAddress(s, context); - sh->filesz = getWord(s, context); - sh->memsz = getWord(s, context); -} - -class ElfMapper -{ -public: - ElfMapper(const ElfReader *reader) : file(reader->m_binary) {} - - bool map() - { - if (!file.open(QIODevice::ReadOnly)) - return false; - - fdlen = quint64(file.size()); - ustart = file.map(0, qint64(fdlen)); - if (ustart == 0) { - // Try reading the data into memory instead. - raw = file.readAll(); - start = raw.constData(); - fdlen = quint64(raw.size()); - } - return true; - } - -public: - QFile file; - QByteArray raw; - union { const char *start; const uchar *ustart; }; - quint64 fdlen; -}; - -ElfReader::ElfReader(const QString &binary) - : m_binary(binary) -{ -} - -ElfData ElfReader::readHeaders() -{ - readIt(); - return m_elfData; -} - -static inline QString msgInvalidElfObject(const QString &binary, const QString &why) -{ - return QStringLiteral("'%1' is an invalid ELF object (%2)") - .arg(QDir::toNativeSeparators(binary), why); -} - -ElfReader::Result ElfReader::readIt() -{ - if (!m_elfData.sectionHeaders.isEmpty()) - return Ok; - if (!m_elfData.programHeaders.isEmpty()) - return Ok; - - ElfMapper mapper(this); - if (!mapper.map()) - return Corrupt; - - const quint64 fdlen = mapper.fdlen; - - if (fdlen < 64) { - m_errorString = QStringLiteral("'%1' is not an ELF object (file too small)").arg(QDir::toNativeSeparators(m_binary)); - return NotElf; - } - - if (strncmp(mapper.start, "\177ELF", 4) != 0) { - m_errorString = QStringLiteral("'%1' is not an ELF object").arg(QDir::toNativeSeparators(m_binary)); - return NotElf; - } - - // 32 or 64 bit - m_elfData.elfclass = ElfClass(mapper.start[4]); - const bool is64Bit = m_elfData.elfclass == Elf_ELFCLASS64; - if (m_elfData.elfclass != Elf_ELFCLASS32 && m_elfData.elfclass != Elf_ELFCLASS64) { - m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("odd cpu architecture")); - return Corrupt; - } - - // int bits = (data[4] << 5); - // If you remove this check to read ELF objects of a different arch, - // please make sure you modify the typedefs - // to match the _plugin_ architecture. - // if ((sizeof(void*) == 4 && bits != 32) - // || (sizeof(void*) == 8 && bits != 64)) { - // if (errorString) - // *errorString = QLibrary::QStringLiteral("'%1' is an invalid ELF object (%2)") - // .arg(m_binary).arg("wrong cpu architecture"_L1); - // return Corrupt; - // } - - // Read Endianhness. - m_elfData.endian = ElfEndian(mapper.ustart[5]); - if (m_elfData.endian != Elf_ELFDATA2LSB && m_elfData.endian != Elf_ELFDATA2MSB) { - m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("odd endianness")); - return Corrupt; - } - - const uchar *data = mapper.ustart + 16; // e_ident - m_elfData.elftype = ElfType(getHalfWord(data, m_elfData)); - m_elfData.elfmachine = ElfMachine(getHalfWord(data, m_elfData)); - /* e_version = */ getWord(data, m_elfData); - m_elfData.entryPoint = getAddress(data, m_elfData); - - quint64 e_phoff = getOffset(data, m_elfData); - quint64 e_shoff = getOffset(data, m_elfData); - /* e_flags = */ getWord(data, m_elfData); - - quint32 e_shsize = getHalfWord(data, m_elfData); - - if (e_shsize > fdlen) { - m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_shsize")); - return Corrupt; - } - - quint32 e_phentsize = getHalfWord(data, m_elfData); - if (e_phentsize != (is64Bit ? 56 : 32)) { - m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("invalid structure")); - return ElfReader::Corrupt; - } - quint32 e_phnum = getHalfWord(data, m_elfData); - - quint32 e_shentsize = getHalfWord(data, m_elfData); - - if (e_shentsize % 4) { - m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_shentsize")); - return Corrupt; - } - - quint32 e_shnum = getHalfWord(data, m_elfData); - quint32 e_shtrndx = getHalfWord(data, m_elfData); - if (data != mapper.ustart + (is64Bit ? 64 : 52)) { - m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_phentsize")); - return ElfReader::Corrupt; - } - - if (quint64(e_shnum) * e_shentsize > fdlen) { - const QString reason = QStringLiteral("announced %1 sections, each %2 bytes, exceed file size").arg(e_shnum).arg(e_shentsize); - m_errorString = msgInvalidElfObject(m_binary, reason); - return Corrupt; - } - - quint64 soff = e_shoff + e_shentsize * e_shtrndx; - -// if ((soff + e_shentsize) > fdlen || soff % 4 || soff == 0) { -// m_errorString = QLibrary::QStringLiteral("'%1' is an invalid ELF object (%2)") -// .arg(m_binary) -// .arg("shstrtab section header seems to be at %1"_L1) -// .arg(QString::number(soff, 16)); -// return Corrupt; -// } - - if (e_shoff) { - ElfSectionHeader strtab; - parseSectionHeader(mapper.ustart + soff, &strtab, m_elfData); - const quint64 stringTableFileOffset = strtab.offset; - if (quint32(stringTableFileOffset + e_shentsize) >= fdlen - || stringTableFileOffset == 0) { - const QString reason = QStringLiteral("string table seems to be at 0x%1").arg(soff, 0, 16); - m_errorString = msgInvalidElfObject(m_binary, reason); - return Corrupt; - } - - for (quint32 i = 0; i < e_shnum; ++i) { - const uchar *s = mapper.ustart + e_shoff + i * e_shentsize; - ElfSectionHeader sh; - parseSectionHeader(s, &sh, m_elfData); - - if (stringTableFileOffset + sh.index > fdlen) { - const QString reason = QStringLiteral("section name %1 of %2 behind end of file") - .arg(i).arg(e_shnum); - m_errorString = msgInvalidElfObject(m_binary, reason); - return Corrupt; - } - - sh.name = mapper.start + stringTableFileOffset + sh.index; - if (sh.name == ".gdb_index") { - m_elfData.symbolsType = FastSymbols; - } else if (sh.name == ".debug_info") { - m_elfData.symbolsType = PlainSymbols; - } else if (sh.name == ".gnu_debuglink") { - m_elfData.debugLink = QByteArray(mapper.start + sh.offset); - m_elfData.symbolsType = LinkedSymbols; - } else if (sh.name == ".note.gnu.build-id") { - m_elfData.symbolsType = BuildIdSymbols; - if (sh.size > 16) - m_elfData.buildId = QByteArray(mapper.start + sh.offset + 16, - int(sh.size) - 16).toHex(); - } - m_elfData.sectionHeaders.append(sh); - } - } - - if (e_phoff) { - for (quint32 i = 0; i < e_phnum; ++i) { - const uchar *s = mapper.ustart + e_phoff + i * e_phentsize; - ElfProgramHeader ph; - parseProgramHeader(s, &ph, m_elfData); - m_elfData.programHeaders.append(ph); - } - } - return Ok; -} - -QByteArray ElfReader::readSection(const QByteArray &name) -{ - readIt(); - int i = m_elfData.indexOf(name); - if (i == -1) - return QByteArray(); - - ElfMapper mapper(this); - if (!mapper.map()) - return QByteArray(); - - const ElfSectionHeader §ion = m_elfData.sectionHeaders.at(i); - return QByteArray(mapper.start + section.offset, int(section.size)); -} - -static QByteArray cutout(const char *s) -{ - QByteArray res(s, 80); - const int pos = res.indexOf('\0'); - if (pos != -1) - res.resize(pos - 1); - return res; -} - -QByteArray ElfReader::readCoreName(bool *isCore) -{ - *isCore = false; - - readIt(); - - ElfMapper mapper(this); - if (!mapper.map()) - return QByteArray(); - - if (m_elfData.elftype != Elf_ET_CORE) - return QByteArray(); - - *isCore = true; - - for (int i = 0, n = m_elfData.sectionHeaders.size(); i != n; ++i) - if (m_elfData.sectionHeaders.at(i).type == Elf_SHT_NOTE) { - const ElfSectionHeader &header = m_elfData.sectionHeaders.at(i); - return cutout(mapper.start + header.offset + 0x40); - } - - for (int i = 0, n = m_elfData.programHeaders.size(); i != n; ++i) - if (m_elfData.programHeaders.at(i).type == Elf_PT_NOTE) { - const ElfProgramHeader &header = m_elfData.programHeaders.at(i); - return cutout(mapper.start + header.offset + 0xec); - } - - return QByteArray(); -} - -int ElfData::indexOf(const QByteArray &name) const -{ - for (int i = 0, n = sectionHeaders.size(); i != n; ++i) - if (sectionHeaders.at(i).name == name) - return i; - return -1; -} - -/* Helpers for reading out the .dynamic section containing the dependencies. - * The ".dynamic" section is an array of - * typedef struct { - * Elf32_Sword d_tag; - * union { - * Elf32_Word d_val; - * dElf32_Addr d_ptr; - * } d_un; - * } Elf32_Dyn - * with entries where a tag DT_NEEDED indicates that m_val is an offset into - * the string table ".dynstr". The documentation states that entries with the - * tag DT_STRTAB contain an offset for the string table to be used, but that - * has been found not to contain valid entries. */ - -enum DynamicSectionTags { - DT_NULL = 0, - DT_NEEDED = 1, - DT_STRTAB = 5, - DT_SONAME = 14, - DT_RPATH = 15 -}; - -QList<QByteArray> ElfReader::dependencies() -{ - QList<QByteArray> result; - - ElfMapper mapper(this); - if (!mapper.map()) { - m_errorString = QStringLiteral("Mapper failure"); - return result; - } - quint64 dynStrOffset = 0; - quint64 dynamicOffset = 0; - quint64 dynamicSize = 0; - - const QList<ElfSectionHeader> &headers = readHeaders().sectionHeaders; - for (const ElfSectionHeader &eh : headers) { - if (eh.name == QByteArrayLiteral(".dynstr")) { - dynStrOffset = eh.offset; - } else if (eh.name == QByteArrayLiteral(".dynamic")) { - dynamicOffset = eh.offset; - dynamicSize = eh.size; - } - if (dynStrOffset && dynamicOffset) - break; - } - - if (!dynStrOffset || !dynamicOffset) { - m_errorString = QStringLiteral("Not a dynamically linked executable."); - return result; - } - - const unsigned char *dynamicData = mapper.ustart + dynamicOffset; - const unsigned char *dynamicDataEnd = dynamicData + dynamicSize; - while (dynamicData < dynamicDataEnd) { - const quint32 tag = getWord(dynamicData, m_elfData); - if (tag == DT_NULL) - break; - if (m_elfData.elfclass == Elf_ELFCLASS64) - dynamicData += sizeof(quint32); // padding to d_val/d_ptr. - if (tag == DT_NEEDED) { - const quint32 offset = getWord(dynamicData, m_elfData); - if (m_elfData.elfclass == Elf_ELFCLASS64) - dynamicData += sizeof(quint32); // past d_ptr. - const char *name = mapper.start + dynStrOffset + offset; - result.push_back(name); - } else { - dynamicData += m_elfData.elfclass == Elf_ELFCLASS64 ? 8 : 4; - } - } - return result; -} - -QT_END_NAMESPACE diff --git a/src/tools/windeployqt/elfreader.h b/src/tools/windeployqt/elfreader.h deleted file mode 100644 index bea53c2ee4..0000000000 --- a/src/tools/windeployqt/elfreader.h +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#ifndef ELFREADER_H -#define ELFREADER_H - -#include <QtCore/QList> -#include <QtCore/QString> -#include <QtCore/QtEndian> - -QT_BEGIN_NAMESPACE - -enum ElfProgramHeaderType -{ - Elf_PT_NULL = 0, - Elf_PT_LOAD = 1, - Elf_PT_DYNAMIC = 2, - Elf_PT_INTERP = 3, - Elf_PT_NOTE = 4, - Elf_PT_SHLIB = 5, - Elf_PT_PHDR = 6, - Elf_PT_TLS = 7, - Elf_PT_NUM = 8 -}; - -enum ElfSectionHeaderType -{ - Elf_SHT_NULL = 0, - Elf_SHT_PROGBITS = 1, - Elf_SHT_SYMTAB = 2, - Elf_SHT_STRTAB = 3, - Elf_SHT_RELA = 4, - Elf_SHT_HASH = 5, - Elf_SHT_DYNAMIC = 6, - Elf_SHT_NOTE = 7, - Elf_SHT_NOBITS = 8, - Elf_SHT_REL = 9, - Elf_SHT_SHLIB = 10, - Elf_SHT_DYNSYM = 11, - Elf_SHT_INIT_ARRAY = 14, - Elf_SHT_FINI_ARRAY = 15, - Elf_SHT_PREINIT_ARRAY = 16, - Elf_SHT_GROUP = 17, - Elf_SHT_SYMTAB_SHNDX = 18 -}; - -enum ElfEndian -{ - Elf_ELFDATANONE = 0, - Elf_ELFDATA2LSB = 1, - Elf_ELFDATA2MSB = 2, - Elf_ELFDATANUM = 3 -}; - -enum ElfClass -{ - Elf_ELFCLASS32 = 1, - Elf_ELFCLASS64 = 2 -}; - -enum ElfType -{ - Elf_ET_NONE = 0, - Elf_ET_REL = 1, - Elf_ET_EXEC = 2, - Elf_ET_DYN = 3, - Elf_ET_CORE = 4 -}; - -enum ElfMachine -{ - Elf_EM_386 = 3, - Elf_EM_ARM = 40, - Elf_EM_X86_64 = 62 -}; - -enum DebugSymbolsType -{ - UnknownSymbols = 0, // Unknown. - NoSymbols = 1, // No usable symbols. - LinkedSymbols = 2, // Link to symbols available. - BuildIdSymbols = 4, // BuildId available. - PlainSymbols = 8, // Ordinary symbols available. - FastSymbols = 16 // Dwarf index available. -}; - -class ElfSectionHeader -{ -public: - QByteArray name; - quint32 index; - quint32 type; - quint32 flags; - quint64 offset; - quint64 size; - quint64 addr; -}; - -class ElfProgramHeader -{ -public: - quint32 name; - quint32 type; - quint64 offset; - quint64 filesz; - quint64 memsz; -}; - -class ElfData -{ -public: - ElfData() : symbolsType(UnknownSymbols) {} - int indexOf(const QByteArray &name) const; - -public: - ElfEndian endian; - ElfType elftype; - ElfMachine elfmachine; - ElfClass elfclass; - quint64 entryPoint; - QByteArray debugLink; - QByteArray buildId; - DebugSymbolsType symbolsType; - QList<ElfSectionHeader> sectionHeaders; - QList<ElfProgramHeader> programHeaders; -}; - -class ElfReader -{ -public: - explicit ElfReader(const QString &binary); - enum Result { Ok, NotElf, Corrupt }; - - ElfData readHeaders(); - QByteArray readSection(const QByteArray §ionName); - QString errorString() const { return m_errorString; } - QByteArray readCoreName(bool *isCore); - QList<QByteArray> dependencies(); - -private: - friend class ElfMapper; - Result readIt(); - - QString m_binary; - QString m_errorString; - ElfData m_elfData; -}; - -QT_END_NAMESPACE - -#endif // ELFREADER_H diff --git a/src/tools/windeployqt/main.cpp b/src/tools/windeployqt/main.cpp index 328c8fbd81..dca9132e15 100644 --- a/src/tools/windeployqt/main.cpp +++ b/src/tools/windeployqt/main.cpp @@ -3,6 +3,8 @@ #include "utils.h" #include "qmlutils.h" +#include "qtmoduleinfo.h" +#include "qtplugininfo.h" #include <QtCore/QCommandLineOption> #include <QtCore/QCommandLineParser> @@ -24,162 +26,59 @@ #include <QtCore/private/qconfig_p.h> -#include <bitset> #include <algorithm> +#include <cstdio> #include <iostream> #include <iterator> -#include <cstdio> +#include <unordered_map> QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -using ModuleBitset = std::bitset<62>; +static QtModuleInfoStore qtModuleEntries; -enum QtModule -#if defined(Q_COMPILER_CLASS_ENUM) || defined(Q_CC_MSVC) - : quint64 -#endif -{ - QtBluetoothModule, - QtConcurrentModule, - QtCoreModule, - QtDeclarativeModule, - QtDesignerComponents, - QtDesignerModule, - QtGuiModule, - QtHelpModule, - QtMultimediaModule, - QtMultimediaWidgetsModule, - QtMultimediaQuickModule, - QtNetworkModule, - QtNfcModule, - QtOpenGLModule, - QtOpenGLWidgetsModule, - QtPositioningModule, - QtPrintSupportModule, - QtQmlModule, - QtQuickModule, - QtQuickParticlesModule, - QtScriptModule, - QtScriptToolsModule, - QtSensorsModule, - QtSerialPortModule, - QtSqlModule, - QtSvgModule, - QtSvgWidgetsModule, - QtTestModule, - QtWidgetsModule, - QtWinExtrasModule, - QtXmlModule, - QtQuickWidgetsModule, - QtWebSocketsModule, - QtWebEngineCoreModule, - QtWebEngineModule, - QtWebEngineWidgetsModule, - QtQmlToolingModule, - Qt3DCoreModule, - Qt3DRendererModule, - Qt3DQuickModule, - Qt3DQuickRendererModule, - Qt3DInputModule, - QtLocationModule, - QtWebChannelModule, - QtTextToSpeechModule, - QtSerialBusModule, - QtGamePadModule, - Qt3DAnimationModule, - QtWebViewModule, - Qt3DExtrasModule, - QtShaderToolsModule, - QtUiToolsModule, - QtCore5CompatModule, - QtChartsModule, - QtDataVisualizationModule, - QtRemoteObjectsModule, - QtScxmlModule, - QtNetworkAuthorizationModule, - QtMqttModule, - QtPdfModule, - QtPdfQuickModule, - QtPdfWidgetsModule -}; +#define DECLARE_KNOWN_MODULE(name) \ + static size_t Qt##name ## ModuleId = QtModule::InvalidId -struct QtModuleEntry { - quint64 module; - const char *option; - const char *libraryName; - const char *translation; -}; +DECLARE_KNOWN_MODULE(3DQuick); +DECLARE_KNOWN_MODULE(Core); +DECLARE_KNOWN_MODULE(Designer); +DECLARE_KNOWN_MODULE(DesignerComponents); +DECLARE_KNOWN_MODULE(Gui); +DECLARE_KNOWN_MODULE(Qml); +DECLARE_KNOWN_MODULE(QmlTooling); +DECLARE_KNOWN_MODULE(Quick); +DECLARE_KNOWN_MODULE(WebEngineCore); +DECLARE_KNOWN_MODULE(Widgets); -static QtModuleEntry qtModuleEntries[] = { - { QtBluetoothModule, "bluetooth", "Qt6Bluetooth", nullptr }, - { QtConcurrentModule, "concurrent", "Qt6Concurrent", "qtbase" }, - { QtCoreModule, "core", "Qt6Core", "qtbase" }, - { QtDeclarativeModule, "declarative", "Qt6Declarative", "qtquick1" }, - { QtDesignerModule, "designer", "Qt6Designer", nullptr }, - { QtDesignerComponents, "designercomponents", "Qt6DesignerComponents", nullptr }, - { QtGamePadModule, "gamepad", "Qt6Gamepad", nullptr }, - { QtGuiModule, "gui", "Qt6Gui", "qtbase" }, - { QtHelpModule, "qthelp", "Qt6Help", "qt_help" }, - { QtMultimediaModule, "multimedia", "Qt6Multimedia", "qtmultimedia" }, - { QtMultimediaWidgetsModule, "multimediawidgets", "Qt6MultimediaWidgets", "qtmultimedia" }, - { QtMultimediaQuickModule, "multimediaquick", "Qt6MultimediaQuick_p", "qtmultimedia" }, - { QtNetworkModule, "network", "Qt6Network", "qtbase" }, - { QtNfcModule, "nfc", "Qt6Nfc", nullptr }, - { QtOpenGLModule, "opengl", "Qt6OpenGL", nullptr }, - { QtOpenGLWidgetsModule, "openglwidgets", "Qt6OpenGLWidgets", nullptr }, - { QtPositioningModule, "positioning", "Qt6Positioning", nullptr }, - { QtPrintSupportModule, "printsupport", "Qt6PrintSupport", nullptr }, - { QtQmlModule, "qml", "Qt6Qml", "qtdeclarative" }, - { QtQmlToolingModule, "qmltooling", "qmltooling", nullptr }, - { QtQuickModule, "quick", "Qt6Quick", "qtdeclarative" }, - { QtQuickParticlesModule, "quickparticles", "Qt6QuickParticles", nullptr }, - { QtQuickWidgetsModule, "quickwidgets", "Qt6QuickWidgets", nullptr }, - { QtScriptModule, "script", "Qt6Script", "qtscript" }, - { QtScriptToolsModule, "scripttools", "Qt6ScriptTools", "qtscript" }, - { QtSensorsModule, "sensors", "Qt6Sensors", nullptr }, - { QtSerialPortModule, "serialport", "Qt6SerialPort", "qtserialport" }, - { QtSqlModule, "sql", "Qt6Sql", "qtbase" }, - { QtSvgModule, "svg", "Qt6Svg", nullptr }, - { QtSvgWidgetsModule, "svgwidgets", "Qt6SvgWidgets", nullptr }, - { QtTestModule, "test", "Qt6Test", "qtbase" }, - { QtWebSocketsModule, "websockets", "Qt6WebSockets", nullptr }, - { QtWidgetsModule, "widgets", "Qt6Widgets", "qtbase" }, - { QtWinExtrasModule, "winextras", "Qt6WinExtras", nullptr }, - { QtXmlModule, "xml", "Qt6Xml", "qtbase" }, - { QtWebEngineCoreModule, "webenginecore", "Qt6WebEngineCore", nullptr }, - { QtWebEngineModule, "webengine", "Qt6WebEngine", "qtwebengine" }, - { QtWebEngineWidgetsModule, "webenginewidgets", "Qt6WebEngineWidgets", nullptr }, - { Qt3DCoreModule, "3dcore", "Qt63DCore", nullptr }, - { Qt3DRendererModule, "3drenderer", "Qt63DRender", nullptr }, - { Qt3DQuickModule, "3dquick", "Qt63DQuick", nullptr }, - { Qt3DQuickRendererModule, "3dquickrenderer", "Qt63DQuickRender", nullptr }, - { Qt3DInputModule, "3dinput", "Qt63DInput", nullptr }, - { Qt3DAnimationModule, "3danimation", "Qt63DAnimation", nullptr }, - { Qt3DExtrasModule, "3dextras", "Qt63DExtras", nullptr }, - { QtLocationModule, "geoservices", "Qt6Location", nullptr }, - { QtWebChannelModule, "webchannel", "Qt6WebChannel", nullptr }, - { QtTextToSpeechModule, "texttospeech", "Qt6TextToSpeech", nullptr }, - { QtSerialBusModule, "serialbus", "Qt6SerialBus", nullptr }, - { QtWebViewModule, "webview", "Qt6WebView", nullptr }, - { QtShaderToolsModule, "shadertools", "Qt6ShaderTools", nullptr }, - { QtUiToolsModule, "uitools", "Qt6UiTools", nullptr }, - { QtCore5CompatModule, "core5compat", "Qt6Core5Compat", nullptr}, - { QtChartsModule, "charts", "Qt6Charts", nullptr}, - { QtDataVisualizationModule, "datavisualization", "Qt6DataVisualization", nullptr}, - { QtRemoteObjectsModule, "remoteobjects", "Qt6RemoteObjects", nullptr}, - { QtScxmlModule, "scxml", "Qt6Scxml", nullptr}, - { QtNetworkAuthorizationModule, "networkauthorization", "Qt6NetworkAuth", nullptr }, - { QtMqttModule, "mqtt", "Qt6Mqtt", nullptr}, - { QtPdfModule, "pdf", "Qt6Pdf", nullptr}, - { QtPdfQuickModule, "pdfquick", "Qt6PdfQuick", nullptr}, - { QtPdfWidgetsModule, "pdfwidgets", "Qt6PdfWidgets", nullptr} -}; +#define DEFINE_KNOWN_MODULE(name) \ + m[QLatin1String("Qt6" #name)] = &Qt##name ## ModuleId -enum QtPlugin { - QtVirtualKeyboardPlugin = 0x1 -}; +static void assignKnownModuleIds() +{ + std::unordered_map<QString, size_t *> m; + DEFINE_KNOWN_MODULE(3DQuick); + DEFINE_KNOWN_MODULE(Core); + DEFINE_KNOWN_MODULE(Designer); + DEFINE_KNOWN_MODULE(DesignerComponents); + DEFINE_KNOWN_MODULE(Gui); + DEFINE_KNOWN_MODULE(Qml); + DEFINE_KNOWN_MODULE(QmlTooling); + DEFINE_KNOWN_MODULE(Quick); + DEFINE_KNOWN_MODULE(WebEngineCore); + DEFINE_KNOWN_MODULE(Widgets); + for (size_t i = 0; i < qtModuleEntries.size(); ++i) { + const QtModule &module = qtModuleEntries.moduleById(i); + auto it = m.find(module.name); + if (it == m.end()) + continue; + *(it->second) = i; + } +} + +#undef DECLARE_KNOWN_MODULE +#undef DEFINE_KNOWN_MODULE static const char webEngineProcessC[] = "QtWebEngineProcess"; @@ -189,14 +88,43 @@ static inline QString webProcessBinary(const char *binaryName, Platform p) return (p & WindowsBased) ? webProcess + QStringLiteral(".exe") : webProcess; } +static QString moduleNameToOptionName(const QString &moduleName) +{ + QString result = moduleName + .mid(3) // strip the "Qt6" prefix + .toLower(); + if (result == u"help"_s) + result.prepend("qt"_L1); + return result; +} + static QByteArray formatQtModules(const ModuleBitset &mask, bool option = false) { QByteArray result; for (const auto &qtModule : qtModuleEntries) { - if (mask.test(qtModule.module)) { + if (mask.test(qtModule.id)) { if (!result.isEmpty()) result.append(' '); - result.append(option ? qtModule.option : qtModule.libraryName); + result.append(option + ? moduleNameToOptionName(qtModule.name).toUtf8() + : qtModule.name.toUtf8()); + if (qtModule.internal) + result.append("Internal"); + } + } + return result; +} + +static QString formatQtPlugins(const PluginInformation &pluginInfo) +{ + QString result(u'\n'); + for (const auto &pair : pluginInfo.typeMap()) { + result += pair.first; + result += u": \n"; + for (const QString &plugin : pair.second) { + result += u" "; + result += plugin; + result += u'\n'; } } return result; @@ -204,14 +132,17 @@ static QByteArray formatQtModules(const ModuleBitset &mask, bool option = false) static Platform platformFromMkSpec(const QString &xSpec) { - if (xSpec == "linux-g++"_L1) - return Unix; if (xSpec.startsWith("win32-"_L1)) { if (xSpec.contains("clang-g++"_L1)) return WindowsDesktopClangMinGW; if (xSpec.contains("clang-msvc++"_L1)) return WindowsDesktopClangMsvc; - return xSpec.contains("g++"_L1) ? WindowsDesktopMinGW : WindowsDesktopMsvc; + if (xSpec.contains("arm"_L1)) + return WindowsDesktopMsvcArm; + if (xSpec.contains("G++"_L1)) + return WindowsDesktopMinGW; + + return WindowsDesktopMsvc; } return UnknownPlatform; } @@ -252,10 +183,12 @@ struct Options { bool quickImports = true; bool translations = true; bool systemD3dCompiler = true; + bool systemDxc = true; bool compilerRunTime = false; - unsigned disabledPlugins = 0; bool softwareRasterizer = true; - Platform platform = WindowsDesktopMsvc; + bool ffmpeg = true; + PluginSelections pluginSelections; + Platform platform = WindowsDesktopMsvcIntel; ModuleBitset additionalLibraries; ModuleBitset disabledLibraries; unsigned updateFileFlags = 0; @@ -267,6 +200,8 @@ struct Options { QStringList languages; QString libraryDirectory; QString pluginDirectory; + QString openSslRootDirectory; + QString qmlDirectory; QStringList binaries; JsonOutput *json = nullptr; ListOption list = ListNone; @@ -275,6 +210,8 @@ struct Options { bool dryRun = false; bool patchQt = true; bool ignoreLibraryErrors = false; + bool deployInsightTrackerPlugin = false; + bool forceOpenSslPlugin = false; }; // Return binary to be deployed from folder, ignore pre-existing web engine process. @@ -300,9 +237,96 @@ static QString msgFileDoesNotExist(const QString & file) enum CommandLineParseFlag { CommandLineParseError = 0x1, - CommandLineParseHelpRequested = 0x2 + CommandLineParseHelpRequested = 0x2, + CommandLineVersionRequested = 0x4 }; +static QCommandLineOption createQMakeOption() +{ + return { + u"qmake"_s, + u"Use specified qmake instead of qmake from PATH. Deprecated, use qtpaths instead."_s, + u"path"_s + }; +} + +static QCommandLineOption createQtPathsOption() +{ + return { + u"qtpaths"_s, + u"Use specified qtpaths.exe instead of qtpaths.exe from PATH."_s, + u"path"_s + }; +} + +static QCommandLineOption createVerboseOption() +{ + return { + u"verbose"_s, + u"Verbose level (0-2)."_s, + u"level"_s + }; +} + +static int parseEarlyArguments(const QStringList &arguments, Options *options, + QString *errorMessage) +{ + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + + QCommandLineOption qmakeOption = createQMakeOption(); + parser.addOption(qmakeOption); + + QCommandLineOption qtpathsOption = createQtPathsOption(); + parser.addOption(qtpathsOption); + + QCommandLineOption verboseOption = createVerboseOption(); + parser.addOption(verboseOption); + + // Deliberately don't check for errors. We want to ignore options we don't know about. + parser.parse(arguments); + + if (parser.isSet(qmakeOption) && parser.isSet(qtpathsOption)) { + *errorMessage = QStringLiteral("-qmake and -qtpaths are mutually exclusive."); + return CommandLineParseError; + } + + if (parser.isSet(qmakeOption) && optVerboseLevel >= 1) + std::wcerr << "Warning: -qmake option is deprecated. Use -qtpaths instead.\n"; + + if (parser.isSet(qtpathsOption) || parser.isSet(qmakeOption)) { + const QString qtpathsArg = parser.isSet(qtpathsOption) ? parser.value(qtpathsOption) + : parser.value(qmakeOption); + + const QString qtpathsBinary = QDir::cleanPath(qtpathsArg); + const QFileInfo fi(qtpathsBinary); + if (!fi.exists()) { + *errorMessage = msgFileDoesNotExist(qtpathsBinary); + return CommandLineParseError; + } + + if (!fi.isExecutable()) { + *errorMessage = u'"' + QDir::toNativeSeparators(qtpathsBinary) + + QStringLiteral("\" is not an executable."); + return CommandLineParseError; + } + options->qtpathsBinary = qtpathsBinary; + } + + if (parser.isSet(verboseOption)) { + bool ok; + const QString value = parser.value(verboseOption); + optVerboseLevel = value.toInt(&ok); + if (!ok || optVerboseLevel < 0) { + *errorMessage = QStringLiteral("Invalid value \"%1\" passed for verbose level.") + .arg(value); + return CommandLineParseError; + } + } + + return 0; +} + static inline int parseArguments(const QStringList &arguments, QCommandLineParser *parser, Options *options, QString *errorMessage) { @@ -315,24 +339,16 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse "installation (e.g. <QT_DIR\\bin>) to the PATH variable and then run:\n windeployqt <path-to-app-binary>\n\n" "If your application uses Qt Quick, run:\n windeployqt --qmldir <path-to-app-qml-files> <path-to-app-binary>"_s); const QCommandLineOption helpOption = parser->addHelpOption(); - parser->addVersionOption(); + const QCommandLineOption versionOption = parser->addVersionOption(); QCommandLineOption dirOption(QStringLiteral("dir"), QStringLiteral("Use directory instead of binary directory."), QStringLiteral("directory")); parser->addOption(dirOption); - QCommandLineOption qmakeOption(QStringLiteral("qmake"), - QStringLiteral("Use specified qmake instead of qmake from PATH. " - "Deprecated, use qtpaths instead."), - QStringLiteral("path")); - parser->addOption(qmakeOption); - - QCommandLineOption qtpathsOption( - QStringLiteral("qtpaths"), - QStringLiteral("Use specified qtpaths.exe instead of qtpaths.exe from PATH."), - QStringLiteral("path")); - parser->addOption(qtpathsOption); + // Add early options to have them available in the help text. + parser->addOption(createQMakeOption()); + parser->addOption(createQtPathsOption()); QCommandLineOption libDirOption(QStringLiteral("libdir"), QStringLiteral("Copy libraries to path."), @@ -344,6 +360,17 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse QStringLiteral("path")); parser->addOption(pluginDirOption); + const QCommandLineOption translationDirOption( + u"translationdir"_s, + u"Copy translations to path."_s, + u"path"_s); + parser->addOption(translationDirOption); + + QCommandLineOption qmlDeployDirOption(QStringLiteral("qml-deploy-dir"), + QStringLiteral("Copy qml files to path."), + QStringLiteral("path")); + parser->addOption(qmlDeployDirOption); + QCommandLineOption debugOption(QStringLiteral("debug"), QStringLiteral("Assume debug binaries.")); parser->addOption(debugOption); @@ -379,6 +406,30 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse QStringLiteral("Skip plugin deployment.")); parser->addOption(noPluginsOption); + QCommandLineOption includeSoftPluginsOption(QStringLiteral("include-soft-plugins"), + QStringLiteral("Include in the deployment all relevant plugins by taking into account all soft dependencies.")); + parser->addOption(includeSoftPluginsOption); + + QCommandLineOption skipPluginTypesOption(QStringLiteral("skip-plugin-types"), + QStringLiteral("A comma-separated list of plugin types that are not deployed (qmltooling,generic)."), + QStringLiteral("plugin types")); + parser->addOption(skipPluginTypesOption); + + QCommandLineOption addPluginTypesOption(QStringLiteral("add-plugin-types"), + QStringLiteral("A comma-separated list of plugin types that will be added to deployment (imageformats,iconengines)"), + QStringLiteral("plugin types")); + parser->addOption(addPluginTypesOption); + + QCommandLineOption includePluginsOption(QStringLiteral("include-plugins"), + QStringLiteral("A comma-separated list of individual plugins that will be added to deployment (scene2d,qjpeg)"), + QStringLiteral("plugins")); + parser->addOption(includePluginsOption); + + QCommandLineOption excludePluginsOption(QStringLiteral("exclude-plugins"), + QStringLiteral("A comma-separated list of individual plugins that will not be deployed (qsvg,qpdf)"), + QStringLiteral("plugins")); + parser->addOption(excludePluginsOption); + QCommandLineOption noLibraryOption(QStringLiteral("no-libraries"), QStringLiteral("Skip library deployment.")); parser->addOption(noLibraryOption); @@ -411,15 +462,15 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse QStringLiteral("Skip deployment of the system D3D compiler.")); parser->addOption(noSystemD3DCompilerOption); + QCommandLineOption noSystemDxcOption(QStringLiteral("no-system-dxc-compiler"), + QStringLiteral("Skip deployment of the system DXC (dxcompiler.dll, dxil.dll).")); + parser->addOption(noSystemDxcOption); + QCommandLineOption compilerRunTimeOption(QStringLiteral("compiler-runtime"), QStringLiteral("Deploy compiler runtime (Desktop only).")); parser->addOption(compilerRunTimeOption); - QCommandLineOption noVirtualKeyboardOption(QStringLiteral("no-virtualkeyboard"), - QStringLiteral("Disable deployment of the Virtual Keyboard.")); - parser->addOption(noVirtualKeyboardOption); - QCommandLineOption noCompilerRunTimeOption(QStringLiteral("no-compiler-runtime"), QStringLiteral("Do not deploy compiler runtime (Desktop only).")); parser->addOption(noCompilerRunTimeOption); @@ -432,6 +483,20 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse QStringLiteral("Do not deploy the software rasterizer library.")); parser->addOption(suppressSoftwareRasterizerOption); + QCommandLineOption noFFmpegOption(QStringLiteral("no-ffmpeg"), + QStringLiteral("Do not deploy the FFmpeg libraries.")); + parser->addOption(noFFmpegOption); + + QCommandLineOption forceOpenSslOption(QStringLiteral("force-openssl"), + QStringLiteral("Deploy openssl plugin but ignore openssl library dependency")); + parser->addOption(forceOpenSslOption); + + QCommandLineOption openSslRootOption(QStringLiteral("openssl-root"), + QStringLiteral("Directory containing openSSL libraries."), + QStringLiteral("directory")); + parser->addOption(openSslRootOption); + + QCommandLineOption listOption(QStringLiteral("list"), "Print only the names of the files copied.\n" "Available options:\n" @@ -445,22 +510,29 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse QStringLiteral("option")); parser->addOption(listOption); - QCommandLineOption verboseOption(QStringLiteral("verbose"), - QStringLiteral("Verbose level (0-2)."), - QStringLiteral("level")); - parser->addOption(verboseOption); + // Add early option to have it available in the help text. + parser->addOption(createVerboseOption()); parser->addPositionalArgument(QStringLiteral("[files]"), QStringLiteral("Binaries or directory containing the binary.")); + QCommandLineOption deployInsightTrackerOption(QStringLiteral("deploy-insighttracker"), + QStringLiteral("Deploy insight tracker plugin.")); + // The option will be added to the parser if the module is available (see block below) + bool insightTrackerModuleAvailable = false; + OptionPtrVector enabledModuleOptions; OptionPtrVector disabledModuleOptions; - const int qtModulesCount = int(sizeof(qtModuleEntries) / sizeof(QtModuleEntry)); + const size_t qtModulesCount = qtModuleEntries.size(); enabledModuleOptions.reserve(qtModulesCount); disabledModuleOptions.reserve(qtModulesCount); - for (int i = 0; i < qtModulesCount; ++i) { - const QString option = QLatin1StringView(qtModuleEntries[i].option); - const QString name = QLatin1StringView(qtModuleEntries[i].libraryName); + for (const QtModule &module : qtModuleEntries) { + const QString option = moduleNameToOptionName(module.name); + const QString name = module.name; + if (name == u"InsightTracker") { + parser->addOption(deployInsightTrackerOption); + insightTrackerModuleAvailable = true; + } const QString enabledDescription = QStringLiteral("Add ") + name + QStringLiteral(" module."); CommandLineOptionPtr enabledOption(new QCommandLineOption(option, enabledDescription)); parser->addOption(*enabledOption.data()); @@ -475,6 +547,8 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse const bool success = parser->parse(arguments); if (parser->isSet(helpOption)) return CommandLineParseHelpRequested; + if (parser->isSet(versionOption)) + return CommandLineVersionRequested; if (!success) { *errorMessage = parser->errorText(); return CommandLineParseError; @@ -482,28 +556,43 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse options->libraryDirectory = parser->value(libDirOption); options->pluginDirectory = parser->value(pluginDirOption); + options->translationsDirectory = parser->value(translationDirOption); + options->qmlDirectory = parser->value(qmlDeployDirOption); options->plugins = !parser->isSet(noPluginsOption); options->libraries = !parser->isSet(noLibraryOption); options->translations = !parser->isSet(noTranslationOption); if (parser->isSet(translationOption)) options->languages = parser->value(translationOption).split(u','); options->systemD3dCompiler = !parser->isSet(noSystemD3DCompilerOption); + options->systemDxc = !parser->isSet(noSystemDxcOption); options->quickImports = !parser->isSet(noQuickImportOption); // default to deployment of compiler runtime for windows desktop configurations - if (options->platform == WindowsDesktopMinGW || options->platform == WindowsDesktopMsvc + if (options->platform == WindowsDesktopMinGW || options->platform.testFlags(WindowsDesktopMsvc) || parser->isSet(compilerRunTimeOption)) options->compilerRunTime = true; if (parser->isSet(noCompilerRunTimeOption)) options->compilerRunTime = false; - if (options->compilerRunTime && options->platform != WindowsDesktopMinGW && options->platform != WindowsDesktopMsvc) { + if (options->compilerRunTime && options->platform != WindowsDesktopMinGW + && !options->platform.testFlags(WindowsDesktopMsvc)) { *errorMessage = QStringLiteral("Deployment of the compiler runtime is implemented for Desktop MSVC/g++ only."); return CommandLineParseError; } - if (parser->isSet(noVirtualKeyboardOption)) - options->disabledPlugins |= QtVirtualKeyboardPlugin; + options->pluginSelections.includeSoftPlugins = parser->isSet(includeSoftPluginsOption); + + if (parser->isSet(skipPluginTypesOption)) + options->pluginSelections.disabledPluginTypes = parser->value(skipPluginTypesOption).split(u','); + + if (parser->isSet(addPluginTypesOption)) + options->pluginSelections.enabledPluginTypes = parser->value(addPluginTypesOption).split(u','); + + if (parser->isSet(includePluginsOption)) + options->pluginSelections.includedPlugins = parser->value(includePluginsOption).split(u','); + + if (parser->isSet(excludePluginsOption)) + options->pluginSelections.excludedPlugins = parser->value(excludePluginsOption).split(u','); if (parser->isSet(releaseWithDebugInfoOption)) std::wcerr << "Warning: " << releaseWithDebugInfoOption.names().first() << " is obsolete."; @@ -529,6 +618,20 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse if (parser->isSet(suppressSoftwareRasterizerOption)) options->softwareRasterizer = false; + if (parser->isSet(noFFmpegOption)) + options->ffmpeg = false; + + if (parser->isSet(forceOpenSslOption)) + options->forceOpenSslPlugin = true; + + if (parser->isSet(openSslRootOption)) + options->openSslRootDirectory = parser->value(openSslRootOption); + + if (options->forceOpenSslPlugin && !options->openSslRootDirectory.isEmpty()) { + *errorMessage = QStringLiteral("force-openssl and openssl-root are mutually exclusive"); + return CommandLineParseError; + } + if (parser->isSet(forceOption)) options->updateFileFlags |= ForceUpdateFile; if (parser->isSet(dryRunOption)) { @@ -538,19 +641,21 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse options->patchQt = !parser->isSet(noPatchQtOption); options->ignoreLibraryErrors = parser->isSet(ignoreErrorOption); + if (insightTrackerModuleAvailable) + options->deployInsightTrackerPlugin = parser->isSet(deployInsightTrackerOption); - for (int i = 0; i < qtModulesCount; ++i) { - if (parser->isSet(*enabledModuleOptions.at(i))) - options->additionalLibraries[qtModuleEntries[i].module] = 1; - if (parser->isSet(*disabledModuleOptions.at(i))) - options->disabledLibraries[qtModuleEntries[i].module] = 1; + for (const QtModule &module : qtModuleEntries) { + if (parser->isSet(*enabledModuleOptions.at(module.id))) + options->additionalLibraries[module.id] = 1; + if (parser->isSet(*disabledModuleOptions.at(module.id))) + options->disabledLibraries[module.id] = 1; } // Add some dependencies - if (options->additionalLibraries.test(QtQuickModule)) - options->additionalLibraries[QtQmlModule] = 1; - if (options->additionalLibraries.test(QtDesignerComponents)) - options->additionalLibraries[QtDesignerModule] = 1; + if (options->additionalLibraries.test(QtQuickModuleId)) + options->additionalLibraries[QtQmlModuleId] = 1; + if (options->additionalLibraries.test(QtDesignerComponentsModuleId)) + options->additionalLibraries[QtDesignerModuleId] = 1; if (parser->isSet(listOption)) { const QString value = parser->value(listOption); @@ -571,16 +676,6 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse if (parser->isSet(jsonOption) || options->list) { optVerboseLevel = 0; options->json = new JsonOutput; - } else { - if (parser->isSet(verboseOption)) { - bool ok; - const QString value = parser->value(verboseOption); - optVerboseLevel = value.toInt(&ok); - if (!ok || optVerboseLevel < 0) { - *errorMessage = QStringLiteral("Invalid value \"%1\" passed for verbose level.").arg(value); - return CommandLineParseError; - } - } } const QStringList posArgs = parser->positionalArguments(); @@ -592,33 +687,6 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse if (parser->isSet(dirOption)) options->directory = parser->value(dirOption); - if (parser->isSet(qmakeOption) && parser->isSet(qtpathsOption)) { - *errorMessage = QStringLiteral("-qmake and -qtpaths are mutually exclusive."); - return CommandLineParseError; - } - - if (parser->isSet(qmakeOption) && optVerboseLevel >= 1) - std::wcerr << "Warning: -qmake option is deprecated. Use -qpaths instead.\n"; - - if (parser->isSet(qtpathsOption) || parser->isSet(qmakeOption)) { - const QString qtpathsArg = parser->isSet(qtpathsOption) ? parser->value(qtpathsOption) - : parser->value(qmakeOption); - - const QString qtpathsBinary = QDir::cleanPath(qtpathsArg); - const QFileInfo fi(qtpathsBinary); - if (!fi.exists()) { - *errorMessage = msgFileDoesNotExist(qtpathsBinary); - return CommandLineParseError; - } - - if (!fi.isExecutable()) { - *errorMessage = u'"' + QDir::toNativeSeparators(qtpathsBinary) - + QStringLiteral("\" is not an executable."); - return CommandLineParseError; - } - options->qtpathsBinary = qtpathsBinary; - } - if (parser->isSet(qmlDirOption)) options->qmlDirectories = parser->values(qmlDirOption); @@ -652,6 +720,7 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse } // directory. // Remaining files or plugin directories + bool multipleDirs = false; for (int i = 1; i < posArgs.size(); ++i) { const QFileInfo fi(QDir::cleanPath(posArgs.at(i))); const QString path = fi.absoluteFilePath(); @@ -665,10 +734,17 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse for (const QString &library : libraries) options->binaries.append(path + u'/' + library); } else { + if (!parser->isSet(dirOption) && fi.absolutePath() != options->directory) + multipleDirs = true; options->binaries.append(path); } } - options->translationsDirectory = options->directory + "/translations"_L1; + if (multipleDirs) { + std::wcerr << "Warning: using binaries from different directories, deploying to following path: " + << options->directory << '\n' << "To disable this warning, use the --dir option\n"; + } + if (options->translationsDirectory.isEmpty()) + options->translationsDirectory = options->directory + "/translations"_L1; return 0; } @@ -685,12 +761,16 @@ static inline QString lineBreak(QString s) return s; } -static inline QString helpText(const QCommandLineParser &p) +static inline QString helpText(const QCommandLineParser &p, const PluginInformation &pluginInfo) { QString result = p.helpText(); // Replace the default-generated text which is too long by a short summary // explaining how to enable single libraries. - const qsizetype moduleStart = result.indexOf("\n --bluetooth"_L1); + if (qtModuleEntries.size() == 0) + return result; + const QtModule &firstModule = qtModuleEntries.moduleById(0); + const QString firstModuleOption = moduleNameToOptionName(firstModule.name); + const qsizetype moduleStart = result.indexOf("\n --"_L1 + firstModuleOption); const qsizetype argumentsStart = result.lastIndexOf("\nArguments:"_L1); if (moduleStart >= argumentsStart) return result; @@ -698,8 +778,20 @@ static inline QString helpText(const QCommandLineParser &p) moduleHelp += "\n\nQt libraries can be added by passing their name (-xml) or removed by passing\n" "the name prepended by --no- (--no-xml). Available libraries:\n"_L1; - moduleHelp += lineBreak(QString::fromLatin1(formatQtModules(0xFFFFFFFFFFFFFFFFull, true))); - moduleHelp += u'\n'; + ModuleBitset mask; + moduleHelp += lineBreak(QString::fromLatin1(formatQtModules(mask.set(), true))); + moduleHelp += u"\n\n"; + moduleHelp += + u"Qt plugins can be included or excluded individually or by type.\n" + u"To deploy or block plugins individually, use the --include-plugins\n" + u"and --exclude-plugins options (--include-plugins qjpeg,qsvgicon)\n" + u"You can also use the --skip-plugin-types or --add-plugin-types to\n" + u"achieve similar results with entire plugin groups, like imageformats, e.g.\n" + u"(--add-plugin-types imageformats,iconengines). Exclusion always takes\n" + u"precedence over inclusion, and types take precedence over specific plugins.\n" + u"For example, including qjpeg, but skipping imageformats, will NOT deploy qjpeg.\n" + u"\nDetected available plugins:\n"; + moduleHelp += formatQtPlugins(pluginInfo); result.replace(moduleStart, argumentsStart - moduleStart, moduleHelp); return result; } @@ -723,7 +815,8 @@ static bool findDependentQtLibraries(const QString &qtBinDir, const QString &bin QStringList dependentLibs; if (directDependencyCount) *directDependencyCount = 0; - if (!readExecutable(binary, platform, errorMessage, &dependentLibs, wordSize, isDebug, machineArch)) { + if (!readPeExecutable(binary, errorMessage, &dependentLibs, wordSize, isDebug, + platform == WindowsDesktopMinGW, machineArch)) { errorMessage->prepend("Unable to find dependent libraries of "_L1 + QDir::toNativeSeparators(binary) + " :"_L1); return false; @@ -788,13 +881,19 @@ public: SkipSources = 0x2 }; - explicit QmlDirectoryFileEntryFunction(Platform platform, DebugMatchMode debugMatchMode, unsigned flags) + explicit QmlDirectoryFileEntryFunction( + const QString &moduleSourcePath, Platform platform, DebugMatchMode debugMatchMode, unsigned flags) : m_flags(flags), m_qmlNameFilter(QmlDirectoryFileEntryFunction::qmlNameFilters(flags)) - , m_dllFilter(platform, debugMatchMode) + , m_dllFilter(platform, debugMatchMode), m_moduleSourcePath(moduleSourcePath) {} QStringList operator()(const QDir &dir) const { + if (moduleSourceDir(dir).canonicalPath() != m_moduleSourcePath) { + // If we're in a different module, return nothing. + return {}; + } + QStringList result; const QStringList &libraries = m_dllFilter(dir); for (const QString &library : libraries) { @@ -810,6 +909,17 @@ public: } private: + static QDir moduleSourceDir(const QDir &dir) + { + QDir moduleSourceDir = dir; + while (!moduleSourceDir.exists(QStringLiteral("qmldir"))) { + if (!moduleSourceDir.cdUp()) { + return {}; + } + } + return moduleSourceDir; + } + static inline QStringList qmlNameFilters(unsigned flags) { QStringList result; @@ -826,66 +936,10 @@ private: const unsigned m_flags; NameFilterFileEntryFunction m_qmlNameFilter; DllDirectoryFileEntryFunction m_dllFilter; + QString m_moduleSourcePath; }; -struct PluginModuleMapping -{ - const char *directoryName; - quint64 module; -}; - -static const PluginModuleMapping pluginModuleMappings[] = -{ - {"qml1tooling", QtDeclarativeModule}, -#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) - {"gamepads", QtGamePadModule}, -#endif - {"accessible", QtGuiModule}, - {"iconengines", QtGuiModule}, - {"imageformats", QtGuiModule}, - {"platforms", QtGuiModule}, - {"platforminputcontexts", QtGuiModule}, - {"virtualkeyboard", QtGuiModule}, - {"geoservices", QtLocationModule}, - {"audio", QtMultimediaModule}, - {"mediaservice", QtMultimediaModule}, - {"multimedia", QtMultimediaModule}, - {"playlistformats", QtMultimediaModule}, - {"networkaccess", QtNetworkModule}, - {"networkinformation", QtNetworkModule}, - {"tls", QtNetworkModule}, - {"position", QtPositioningModule}, - {"printsupport", QtPrintSupportModule}, - {"scenegraph", QtQuickModule}, - {"qmltooling", QtQuickModule | QtQmlToolingModule}, - {"sensors", QtSensorsModule}, - {"sensorgestures", QtSensorsModule}, - {"canbus", QtSerialBusModule}, - {"sqldrivers", QtSqlModule}, -#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) - {"texttospeech", QtTextToSpeechModule}, -#endif - {"qtwebengine", QtWebEngineModule | QtWebEngineCoreModule | QtWebEngineWidgetsModule}, - {"styles", QtWidgetsModule}, - {"sceneparsers", Qt3DRendererModule}, - {"renderers", Qt3DRendererModule | QtShaderToolsModule}, - {"renderplugins", Qt3DRendererModule}, - {"geometryloaders", Qt3DRendererModule}, - {"webview", QtWebViewModule}, - {"designer", QtUiToolsModule}, - {"scxmldatamodel", QtScxmlModule} -}; - -static inline quint64 qtModuleForPlugin(const QString &subDirName) -{ - const auto end = std::end(pluginModuleMappings); - const auto result = - std::find_if(std::begin(pluginModuleMappings), end, - [&subDirName] (const PluginModuleMapping &m) { return subDirName == QLatin1StringView(m.directoryName); }); - return result != end ? result->module : 0; // "designer" -} - -static quint64 qtModule(QString module, const QString &infix) +static qint64 qtModule(QString module, const QString &infix) { // Match needle 'path/Qt6Core<infix><d>.dll' or 'path/libQt6Core<infix>.so.5.0' const qsizetype lastSlashPos = module.lastIndexOf(u'/'); @@ -900,124 +954,189 @@ static quint64 qtModule(QString module, const QString &infix) module.truncate(endPos); // That should leave us with 'Qt6Core<d>'. for (const auto &qtModule : qtModuleEntries) { - const QLatin1StringView libraryName(qtModule.libraryName); + const QString &libraryName = qtModule.name; if (module == libraryName || (module.size() == libraryName.size() + 1 && module.startsWith(libraryName))) { - return qtModule.module; + return qtModule.id; } } - return 0; + std::wcerr << "Warning: module " << qPrintable(module) << " could not be found\n"; + return -1; } // Return the path if a plugin is to be deployed -static QString deployPlugin(const QString &plugin, const QDir &subDir, - ModuleBitset *usedQtModules, const ModuleBitset &disabledQtModules, - unsigned disabledPlugins, - const QString &libraryLocation, const QString &infix, - Platform platform) +static QString deployPlugin(const QString &plugin, const QDir &subDir, const bool dueToModule, + const DebugMatchMode &debugMatchMode, ModuleBitset *pluginNeededQtModules, + const ModuleBitset &disabledQtModules, + const PluginSelections &pluginSelections, const QString &libraryLocation, + const QString &infix, Platform platform, + bool deployInsightTrackerPlugin, bool deployOpenSslPlugin) { + const QString subDirName = subDir.dirName(); // Filter out disabled plugins - if ((disabledPlugins & QtVirtualKeyboardPlugin) - && plugin.startsWith("qtvirtualkeyboardplugin"_L1)) { + if (optVerboseLevel && pluginSelections.disabledPluginTypes.contains(subDirName)) { + std::wcout << "Skipping plugin " << plugin << " due to skipped plugin type " << subDirName << '\n'; + return {}; + } + if (optVerboseLevel && subDirName == u"generic" && plugin.contains(u"qinsighttracker") + && !deployInsightTrackerPlugin) { + std::wcout << "Skipping plugin " << plugin + << ". Use -deploy-insighttracker if you want to use it.\n"; + return {}; + } + if (optVerboseLevel && subDirName == u"tls" && plugin.contains(u"qopensslbackend") + && !deployOpenSslPlugin) { + std::wcout << "Skipping plugin " << plugin + << ". Use -force_openssl or specify -openssl-root if you want to use it.\n"; + return {}; + } + + const int dotIndex = plugin.lastIndexOf(u'.'); + // Strip the .dll from the name, and an additional 'd' if it's a debug library with the 'd' + // suffix + const int stripIndex = debugMatchMode == MatchDebug && platformHasDebugSuffix(platform) + ? dotIndex - 1 + : dotIndex; + const QString pluginName = plugin.first(stripIndex); + + if (optVerboseLevel && pluginSelections.excludedPlugins.contains(pluginName)) { + std::wcout << "Skipping plugin " << plugin << " due to exclusion option" << '\n'; + return {}; + } + + // By default, only deploy qwindows.dll + if (subDirName == u"platforms" + && !(pluginSelections.includedPlugins.contains(pluginName) + || (pluginSelections.enabledPluginTypes.contains(subDirName))) + && !pluginName.startsWith(u"qwindows")) { return {}; } const QString pluginPath = subDir.absoluteFilePath(plugin); + + // If dueToModule is false, check if the user included the plugin or the entire type. In the + // former's case, only deploy said plugin and not all plugins of that type. + const bool requiresPlugin = pluginSelections.includedPlugins.contains(pluginName) + || pluginSelections.enabledPluginTypes.contains(subDirName); + if (!dueToModule && !requiresPlugin) + return {}; + // Deploy QUiTools plugins as is without further dependency checking. // The user needs to ensure all required libraries are present (would // otherwise pull QtWebEngine for its plugin). - if (subDir.dirName() == u"designer") + if (subDirName == u"designer") return pluginPath; QStringList dependentQtLibs; - ModuleBitset neededModules; QString errorMessage; if (findDependentQtLibraries(libraryLocation, pluginPath, platform, &errorMessage, &dependentQtLibs)) { - for (int d = 0; d < dependentQtLibs.size(); ++ d) - neededModules[qtModule(dependentQtLibs.at(d), infix)] = 1; + for (int d = 0; d < dependentQtLibs.size(); ++d) { + const qint64 module = qtModule(dependentQtLibs.at(d), infix); + if (module >= 0) + (*pluginNeededQtModules)[module] = 1; + } } else { std::wcerr << "Warning: Cannot determine dependencies of " << QDir::toNativeSeparators(pluginPath) << ": " << errorMessage << '\n'; } - ModuleBitset missingModules; - missingModules = neededModules & disabledQtModules; - if (missingModules.any()) { + ModuleBitset disabledNeededQtModules; + disabledNeededQtModules = *pluginNeededQtModules & disabledQtModules; + if (disabledNeededQtModules.any()) { if (optVerboseLevel) { std::wcout << "Skipping plugin " << plugin << " due to disabled dependencies (" - << formatQtModules(missingModules).constData() << ").\n"; + << formatQtModules(disabledNeededQtModules).constData() << ").\n"; } return {}; } - missingModules = (neededModules & ~*usedQtModules); - if (missingModules.any()) { - *usedQtModules |= missingModules; - if (optVerboseLevel) { - std::wcout << "Adding " << formatQtModules(missingModules).constData() - << " for " << plugin << '\n'; - } - } return pluginPath; } +static bool needsPluginType(const QString &subDirName, const PluginInformation &pluginInfo, + const PluginSelections &pluginSelections) +{ + bool needsTypeForPlugin = false; + for (const QString &plugin: pluginSelections.includedPlugins) { + if (pluginInfo.isTypeForPlugin(subDirName, plugin)) + needsTypeForPlugin = true; + } + return (pluginSelections.enabledPluginTypes.contains(subDirName) || needsTypeForPlugin); +} + QStringList findQtPlugins(ModuleBitset *usedQtModules, const ModuleBitset &disabledQtModules, - unsigned disabledPlugins, + const PluginInformation &pluginInfo, const PluginSelections &pluginSelections, const QString &qtPluginsDirName, const QString &libraryLocation, - const QString &infix, - DebugMatchMode debugMatchModeIn, Platform platform, QString *platformPlugin) + const QString &infix, DebugMatchMode debugMatchModeIn, Platform platform, + QString *platformPlugin, bool deployInsightTrackerPlugin, + bool deployOpenSslPlugin) { if (qtPluginsDirName.isEmpty()) return QStringList(); QDir pluginsDir(qtPluginsDirName); QStringList result; + bool missingQtModulesAdded = false; const QFileInfoList &pluginDirs = pluginsDir.entryInfoList(QStringList(u"*"_s), QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &subDirFi : pluginDirs) { const QString subDirName = subDirFi.fileName(); - const quint64 module = qtModuleForPlugin(subDirName); - if (usedQtModules->test(module)) { - const DebugMatchMode debugMatchMode = (module & QtWebEngineCoreModule) + const size_t module = qtModuleEntries.moduleIdForPluginType(subDirName); + if (module == QtModule::InvalidId) { + if (optVerboseLevel > 1) { + std::wcerr << "No Qt module found for plugin type \"" << subDirName << "\".\n"; + } + continue; + } + const bool dueToModule = usedQtModules->test(module); + if (dueToModule || needsPluginType(subDirName, pluginInfo, pluginSelections)) { + const DebugMatchMode debugMatchMode = (module == QtWebEngineCoreModuleId) ? MatchDebugOrRelease // QTBUG-44331: Debug detection does not work for webengine, deploy all. : debugMatchModeIn; QDir subDir(subDirFi.absoluteFilePath()); - // Filter out disabled plugins - if ((disabledPlugins & QtVirtualKeyboardPlugin) && subDirName == "virtualkeyboard"_L1) - continue; - if (disabledQtModules.test(QtQmlToolingModule) && subDirName == "qmltooling"_L1) - continue; - // Filter for platform or any. - QString filter; + if (optVerboseLevel) + std::wcout << "Adding in plugin type " << subDirFi.baseName() << " for module: " << qtModuleEntries.moduleById(module).name << '\n'; + const bool isPlatformPlugin = subDirName == "platforms"_L1; - if (isPlatformPlugin) { - switch (platform) { - case WindowsDesktopMsvc: - case WindowsDesktopMinGW: - filter = QStringLiteral("qwindows"); - break; - case Unix: - filter = QStringLiteral("libqxcb"); - break; - case UnknownPlatform: - break; - } - } else { - filter = u"*"_s; - } - const QStringList plugins = findSharedLibraries(subDir, platform, debugMatchMode, filter); + const QStringList plugins = + findSharedLibraries(subDir, platform, debugMatchMode, QString()); for (const QString &plugin : plugins) { + ModuleBitset pluginNeededQtModules; const QString pluginPath = - deployPlugin(plugin, subDir, usedQtModules, disabledQtModules, - disabledPlugins, libraryLocation, infix, platform); + deployPlugin(plugin, subDir, dueToModule, debugMatchMode, &pluginNeededQtModules, + disabledQtModules, pluginSelections, libraryLocation, infix, + platform, deployInsightTrackerPlugin, deployOpenSslPlugin); if (!pluginPath.isEmpty()) { - if (isPlatformPlugin) + if (isPlatformPlugin && plugin.startsWith(u"qwindows")) *platformPlugin = subDir.absoluteFilePath(plugin); result.append(pluginPath); + + const ModuleBitset missingModules = (pluginNeededQtModules & ~*usedQtModules); + if (missingModules.any()) { + *usedQtModules |= missingModules; + missingQtModulesAdded = true; + if (optVerboseLevel) { + std::wcout << "Adding " << formatQtModules(missingModules).constData() + << " for " << plugin << " from plugin type: " << subDirName << '\n'; + } + } } } // for filter } // type matches } // for plugin folder + + // If missing Qt modules were added during plugin deployment make additional pass, because we may need + // additional plugins. + if (pluginSelections.includeSoftPlugins && missingQtModulesAdded) { + if (optVerboseLevel) { + std::wcout << "Performing additional pass of finding Qt plugins due to updated Qt module list: " + << formatQtModules(*usedQtModules).constData() << "\n"; + } + return findQtPlugins(usedQtModules, disabledQtModules, pluginInfo, pluginSelections, qtPluginsDirName, + libraryLocation, infix, debugMatchModeIn, platform, platformPlugin, + deployInsightTrackerPlugin, deployOpenSslPlugin); + } + return result; } @@ -1025,9 +1144,8 @@ static QStringList translationNameFilters(const ModuleBitset &modules, const QSt { QStringList result; for (const auto &qtModule : qtModuleEntries) { - if (modules.test(qtModule.module) && qtModule.translation) { - const QString name = QLatin1StringView(qtModule.translation) + - u'_' + prefix + ".qm"_L1; + if (modules.test(qtModule.id) && !qtModule.translationCatalog.isEmpty()) { + const QString name = qtModule.translationCatalog + u'_' + prefix + ".qm"_L1; if (!result.contains(name)) result.push_back(name); } @@ -1067,7 +1185,12 @@ static bool deployTranslations(const QString &sourcePath, const ModuleBitset &us if (options.json) options.json->addFile(sourcePath + u'/' + targetFile, absoluteTarget); arguments.append(QDir::toNativeSeparators(targetFilePath)); - const QFileInfoList &langQmFiles = sourceDir.entryInfoList(translationNameFilters(usedQtModules, prefix)); + const QStringList translationFilters = translationNameFilters(usedQtModules, prefix); + if (translationFilters.isEmpty()){ + std::wcerr << "Warning: translation catalogs are all empty, skipping translation deployment\n"; + return true; + } + const QFileInfoList &langQmFiles = sourceDir.entryInfoList(translationFilters); for (const QFileInfo &langQmFileFi : langQmFiles) { if (options.json) { options.json->addFile(langQmFileFi.absoluteFilePath(), @@ -1087,6 +1210,62 @@ static bool deployTranslations(const QString &sourcePath, const ModuleBitset &us return true; } +static QStringList findFFmpegLibs(const QString &qtBinDir, Platform platform) +{ + const std::vector<QLatin1StringView> ffmpegHints = { "avcodec"_L1, "avformat"_L1, "avutil"_L1, + "swresample"_L1, "swscale"_L1 }; + const QStringList bundledLibs = + findSharedLibraries(qtBinDir, platform, MatchDebugOrRelease, {}); + + QStringList ffmpegLibs; + for (const QLatin1StringView &libHint : ffmpegHints) { + const QStringList ffmpegLib = bundledLibs.filter(libHint, Qt::CaseInsensitive); + + if (ffmpegLib.empty()) { + std::wcerr << "Warning: Cannot find FFmpeg libraries. Multimedia features will not work as expected.\n"; + return {}; + } else if (ffmpegLib.size() != 1u) { + std::wcerr << "Warning: Multiple versions of FFmpeg libraries found. Multimedia features will not work as expected.\n"; + return {}; + } + + const QChar slash(u'/'); + QFileInfo ffmpegLibPath{ qtBinDir + slash + ffmpegLib.front() }; + ffmpegLibs.append(ffmpegLibPath.absoluteFilePath()); + } + + return ffmpegLibs; +} + +// Find the openssl libraries Qt executables depend on. +static QStringList findOpenSslLibraries(const QString &openSslRootDir, Platform platform) +{ + const std::vector<QLatin1StringView> libHints = { "libcrypto"_L1, "libssl"_L1 }; + const QChar slash(u'/'); + const QString openSslBinDir = openSslRootDir + slash + "bin"_L1; + const QStringList openSslRootLibs = + findSharedLibraries(openSslBinDir, platform, MatchDebugOrRelease, {}); + + QStringList result; + for (const QLatin1StringView &libHint : libHints) { + const QStringList lib = openSslRootLibs.filter(libHint, Qt::CaseInsensitive); + + if (lib.empty()) { + std::wcerr << "Warning: Cannot find openssl libraries.\n"; + return {}; + } else if (lib.size() != 1u) { + std::wcerr << "Warning: Multiple versions of openssl libraries found.\n"; + return {}; + } + + QFileInfo libPath{ openSslBinDir + slash + lib.front() }; + result.append(libPath.absoluteFilePath()); + } + + return result; +} + + struct DeployResult { operator bool() const { return success; } @@ -1099,20 +1278,14 @@ struct DeployResult }; static QString libraryPath(const QString &libraryLocation, const char *name, - const QString &qtLibInfix, Platform platform, bool debug) + const QString &infix, Platform platform, bool debug) { QString result = libraryLocation + u'/'; - if (platform & WindowsBased) { - result += QLatin1StringView(name); - result += qtLibInfix; - if (debug && platformHasDebugSuffix(platform)) - result += u'd'; - } else if (platform.testFlag(UnixBased)) { - result += QStringLiteral("lib"); - result += QLatin1StringView(name); - result += qtLibInfix; - } - result += sharedLibrarySuffix(platform); + result += QLatin1StringView(name); + result += infix; + if (debug && platformHasDebugSuffix(platform)) + result += u'd'; + result += sharedLibrarySuffix(); return result; } @@ -1153,29 +1326,51 @@ static QString vcRedistDir() return QString(); } -static QStringList compilerRunTimeLibs(Platform platform, bool isDebug, unsigned short machineArch) +static QStringList findMinGWRuntimePaths(const QString &qtBinDir, Platform platform, const QStringList &runtimeFilters) { + //MinGW: Add runtime libraries. Check first for the Qt binary directory, and default to path if nothing is found. QStringList result; - switch (platform) { - case WindowsDesktopMinGW: { // MinGW: Add runtime libraries - static const char *minGwRuntimes[] = {"*gcc_", "*stdc++", "*winpthread"}; - const QString gcc = findInPath(QStringLiteral("g++.exe")); - if (gcc.isEmpty()) { - std::wcerr << "Warning: Cannot find GCC installation directory. g++.exe must be in the path.\n"; - break; + const bool isClang = platform == WindowsDesktopClangMinGW; + QStringList filters; + const QString suffix = u'*' + sharedLibrarySuffix(); + for (const auto &minGWRuntime : runtimeFilters) + filters.append(minGWRuntime + suffix); + + QFileInfoList dlls = QDir(qtBinDir).entryInfoList(filters, QDir::Files); + if (dlls.isEmpty()) { + std::wcerr << "Warning: Runtime libraries not found in Qt binary folder, defaulting to looking in path\n"; + const QString binaryPath = isClang ? findInPath("clang++.exe"_L1) : findInPath("g++.exe"_L1); + if (binaryPath.isEmpty()) { + std::wcerr << "Warning: Cannot find " << (isClang ? "Clang" : "GCC") << " installation directory, " << (isClang ? "clang++" : "g++") << ".exe must be in the path\n"; + return {}; } - const QString binPath = QFileInfo(gcc).absolutePath(); - QStringList filters; - const QString suffix = u'*' + sharedLibrarySuffix(platform); - for (auto minGwRuntime : minGwRuntimes) - filters.append(QLatin1StringView(minGwRuntime) + suffix); - const QFileInfoList &dlls = QDir(binPath).entryInfoList(filters, QDir::Files); - for (const QFileInfo &dllFi : dlls) - result.append(dllFi.absoluteFilePath()); + const QString binaryFolder = QFileInfo(binaryPath).absolutePath(); + dlls = QDir(binaryFolder).entryInfoList(filters, QDir::Files); + } + + for (const QFileInfo &dllFi : dlls) + result.append(dllFi.absoluteFilePath()); + + return result; +} + +static QStringList compilerRunTimeLibs(const QString &qtBinDir, Platform platform, bool isDebug, unsigned short machineArch) +{ + QStringList result; + switch (platform) { + case WindowsDesktopMinGW: { + const QStringList minGWRuntimes = { "*gcc_"_L1, "*stdc++"_L1, "*winpthread"_L1 }; + result.append(findMinGWRuntimePaths(qtBinDir, platform, minGWRuntimes)); + break; } + case WindowsDesktopClangMinGW: { + const QStringList clangMinGWRuntimes = { "*unwind"_L1, "*c++"_L1 }; + result.append(findMinGWRuntimePaths(qtBinDir, platform, clangMinGWRuntimes)); break; + } #ifdef Q_OS_WIN - case WindowsDesktopMsvc: { // MSVC/Desktop: Add redistributable packages. + case WindowsDesktopMsvcIntel: + case WindowsDesktopMsvcArm: { // MSVC/Desktop: Add redistributable packages. QString vcRedistDirName = vcRedistDir(); if (vcRedistDirName.isEmpty()) break; @@ -1231,16 +1426,6 @@ static inline int qtVersion(const QMap<QString, QString> &qtpathsVariables) return (majorVersion << 16) | (minorVersion << 8) | patchVersion; } -// Determine the Qt lib infix from the library path of "Qt6Core<qtblibinfix>[d].dll". -static inline QString qtlibInfixFromCoreLibName(const QString &path, bool isDebug, Platform platform) -{ - const qsizetype startPos = path.lastIndexOf(u'/') + 8; - qsizetype endPos = path.lastIndexOf(u'.'); - if (isDebug && (platform & WindowsBased)) - endPos--; - return endPos > startPos ? path.mid(startPos, endPos - startPos) : QString(); -} - // Deploy a library along with its .pdb debug info file (MSVC) should it exist. static bool updateLibrary(const QString &sourceFileName, const QString &targetDirectory, const Options &options, QString *errorMessage) @@ -1273,16 +1458,14 @@ static QString getIcuVersion(const QString &libName) } static DeployResult deploy(const Options &options, const QMap<QString, QString> &qtpathsVariables, - QString *errorMessage) + const PluginInformation &pluginInfo, QString *errorMessage) { DeployResult result; const QChar slash = u'/'; const QString qtBinDir = qtpathsVariables.value(QStringLiteral("QT_INSTALL_BINS")); - const QString libraryLocation = options.platform == Unix - ? qtpathsVariables.value(QStringLiteral("QT_INSTALL_LIBS")) - : qtBinDir; + const QString libraryLocation = qtBinDir; const QString infix = qtpathsVariables.value(QLatin1StringView(qmakeInfixKey)); const int version = qtVersion(qtpathsVariables); Q_UNUSED(version); @@ -1329,19 +1512,17 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString> // Determine application type, check Quick2 is used by looking at the // direct dependencies (do not be fooled by QtWebKit depending on it). - QString qtLibInfix; - for (int m = 0; m < directDependencyCount; ++m) { - const quint64 module = qtModule(dependentQtLibs.at(m), infix); - result.directlyUsedQtLibraries[module] = 1; - if (module == QtCoreModule) - qtLibInfix = qtlibInfixFromCoreLibName(dependentQtLibs.at(m), detectedDebug, options.platform); + for (int m = 0; m < dependentQtLibs.size(); ++m) { + const qint64 module = qtModule(dependentQtLibs.at(m), infix); + if (module >= 0) + result.directlyUsedQtLibraries[module] = 1; } - const bool usesQml = result.directlyUsedQtLibraries.test(QtQmlModule); - const bool usesQuick = result.directlyUsedQtLibraries.test(QtQuickModule); - const bool uses3DQuick = result.directlyUsedQtLibraries.test(Qt3DQuickModule); - const bool usesQml2 = !(options.disabledLibraries.test(QtQmlModule)) - && (usesQml || usesQuick || uses3DQuick || (options.additionalLibraries.test(QtQmlModule))); + const bool usesQml = result.directlyUsedQtLibraries.test(QtQmlModuleId); + const bool usesQuick = result.directlyUsedQtLibraries.test(QtQuickModuleId); + const bool uses3DQuick = result.directlyUsedQtLibraries.test(Qt3DQuickModuleId); + const bool usesQml2 = !(options.disabledLibraries.test(QtQmlModuleId)) + && (usesQml || usesQuick || uses3DQuick || (options.additionalLibraries.test(QtQmlModuleId))); if (optVerboseLevel) { std::wcout << QDir::toNativeSeparators(options.binaries.first()) << ' ' @@ -1363,7 +1544,7 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString> const QStringList qtLibs = dependentQtLibs.filter(QStringLiteral("Qt6Core"), Qt::CaseInsensitive) + dependentQtLibs.filter(QStringLiteral("Qt5WebKit"), Qt::CaseInsensitive); for (const QString &qtLib : qtLibs) { - QStringList icuLibs = findDependentLibraries(qtLib, options.platform, errorMessage).filter(QStringLiteral("ICU"), Qt::CaseInsensitive); + QStringList icuLibs = findDependentLibraries(qtLib, errorMessage).filter(QStringLiteral("ICU"), Qt::CaseInsensitive); if (!icuLibs.isEmpty()) { // Find out the ICU version to add the data library icudtXX.dll, which does not show // as a dependency. @@ -1372,7 +1553,7 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString> if (optVerboseLevel > 1) std::wcout << "Adding ICU version " << icuVersion << '\n'; QString icuLib = QStringLiteral("icudt") + icuVersion - + QLatin1StringView(windowsSharedLibrarySuffix);; + + QLatin1StringView(windowsSharedLibrarySuffix); // Some packages contain debug dlls of ICU libraries even though it's a C // library and the official packages do not differentiate (QTBUG-87677) if (result.isDebug) { @@ -1415,7 +1596,7 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString> std::wcout << "Scanning " << QDir::toNativeSeparators(qmlDirectory) << ":\n"; const QmlImportScanResult scanResult = runQmlImportScanner(qmlDirectory, qmlImportPaths, - result.directlyUsedQtLibraries.test(QtWidgetsModule), + result.directlyUsedQtLibraries.test(QtWidgetsModuleId), options.platform, debugMatchMode, errorMessage); if (!scanResult.ok) return result; @@ -1445,8 +1626,9 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString> // QtModule enumeration (and thus controlled by flags) and others. QStringList deployedQtLibraries; for (int i = 0 ; i < dependentQtLibs.size(); ++i) { - if (const quint64 qtm = qtModule(dependentQtLibs.at(i), infix)) - result.usedQtLibraries[qtm] = 1; + const qint64 module = qtModule(dependentQtLibs.at(i), infix); + if (module >= 0) + result.usedQtLibraries[module] = 1; else deployedQtLibraries.push_back(dependentQtLibs.at(i)); // Not represented by flag. } @@ -1454,44 +1636,60 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString> ModuleBitset disabled = options.disabledLibraries; if (!usesQml2) { - disabled[QtQmlModule] = 1; - disabled[QtQuickModule] = 1; + disabled[QtQmlModuleId] = 1; + disabled[QtQuickModuleId] = 1; + } + + QStringList openSslLibs; + if (!options.openSslRootDirectory.isEmpty()) { + openSslLibs = findOpenSslLibraries(options.openSslRootDirectory, options.platform); + if (openSslLibs.isEmpty()) { + *errorMessage = QStringLiteral("Unable to find openSSL libraries in ") + + options.openSslRootDirectory; + return result; + } + + deployedQtLibraries.append(openSslLibs); } + const bool deployOpenSslPlugin = options.forceOpenSslPlugin || !openSslLibs.isEmpty(); + const QStringList plugins = findQtPlugins( &result.deployedQtLibraries, // For non-QML applications, disable QML to prevent it from being pulled in by the // qtaccessiblequick plugin. - disabled, - options.disabledPlugins, qtpathsVariables.value(QStringLiteral("QT_INSTALL_PLUGINS")), - libraryLocation, infix, debugMatchMode, options.platform, &platformPlugin); + disabled, pluginInfo, + options.pluginSelections, qtpathsVariables.value(QStringLiteral("QT_INSTALL_PLUGINS")), + libraryLocation, infix, debugMatchMode, options.platform, &platformPlugin, + options.deployInsightTrackerPlugin, deployOpenSslPlugin); // Apply options flags and re-add library names. QString qtGuiLibrary; for (const auto &qtModule : qtModuleEntries) { - if (result.deployedQtLibraries.test(qtModule.module)) { - const QString library = libraryPath(libraryLocation, qtModule.libraryName, qtLibInfix, options.platform, result.isDebug); + if (result.deployedQtLibraries.test(qtModule.id)) { + const QString library = libraryPath(libraryLocation, qtModule.name.toUtf8(), infix, + options.platform, result.isDebug); deployedQtLibraries.append(library); - if (qtModule.module == QtGuiModule) + if (qtModule.id == QtGuiModuleId) qtGuiLibrary = library; } } if (optVerboseLevel >= 1) { - std::wcout << "Direct dependencies: " << formatQtModules(result.directlyUsedQtLibraries.to_ullong()).constData() - << "\nAll dependencies : " << formatQtModules(result.usedQtLibraries.to_ullong()).constData() - << "\nTo be deployed : " << formatQtModules(result.deployedQtLibraries.to_ullong()).constData() << '\n'; + std::wcout << "Direct dependencies: " << formatQtModules(result.directlyUsedQtLibraries).constData() + << "\nAll dependencies : " << formatQtModules(result.usedQtLibraries).constData() + << "\nTo be deployed : " << formatQtModules(result.deployedQtLibraries).constData() << '\n'; } if (optVerboseLevel > 1) std::wcout << "Plugins: " << plugins.join(u',') << '\n'; - if ((result.deployedQtLibraries.test(QtGuiModule)) && platformPlugin.isEmpty()) { + if (result.deployedQtLibraries.test(QtGuiModuleId) && platformPlugin.isEmpty()) { *errorMessage =QStringLiteral("Unable to find the platform plugin."); return result; } if (options.platform.testFlag(WindowsBased) && !qtGuiLibrary.isEmpty()) { - const QStringList guiLibraries = findDependentLibraries(qtGuiLibrary, options.platform, errorMessage); + const QStringList guiLibraries = findDependentLibraries(qtGuiLibrary, errorMessage); const bool dependsOnOpenGl = !guiLibraries.filter(QStringLiteral("opengl32"), Qt::CaseInsensitive).isEmpty(); if (options.softwareRasterizer && !dependsOnOpenGl) { const QFileInfo softwareRasterizer(qtBinDir + slash + QStringLiteral("opengl32sw") + QLatin1StringView(windowsSharedLibrarySuffix)); @@ -1506,15 +1704,28 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString> deployedQtLibraries.push_back(d3dCompiler); } } + if (options.systemDxc) { + const QStringList dxcLibs = findDxc(options.platform, qtBinDir, wordSize); + if (!dxcLibs.isEmpty()) + deployedQtLibraries.append(dxcLibs); + else + std::wcerr << "Warning: Cannot find any version of the dxcompiler.dll and dxil.dll.\n"; + } } // Windows + // Add FFmpeg if we deploy the FFmpeg backend + if (options.ffmpeg + && !plugins.filter(QStringLiteral("ffmpegmediaplugin"), Qt::CaseInsensitive).empty()) { + deployedQtLibraries.append(findFFmpegLibs(qtBinDir, options.platform)); + } + // Update libraries if (options.libraries) { const QString targetPath = options.libraryDirectory.isEmpty() ? options.directory : options.libraryDirectory; QStringList libraries = deployedQtLibraries; if (options.compilerRunTime) - libraries.append(compilerRunTimeLibs(options.platform, result.isDebug, machineArch)); + libraries.append(compilerRunTimeLibs(qtBinDir, options.platform, result.isDebug, machineArch)); for (const QString &qtLib : std::as_const(libraries)) { if (!updateLibrary(qtLib, targetPath, options, errorMessage)) return result; @@ -1522,7 +1733,7 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString> #if !QT_CONFIG(relocatable) if (options.patchQt && !options.dryRun) { - const QString qt6CoreName = QFileInfo(libraryPath(libraryLocation, "Qt6Core", qtLibInfix, + const QString qt6CoreName = QFileInfo(libraryPath(libraryLocation, "Qt6Core", infix, options.platform, result.isDebug)).fileName(); if (!patchQtCore(targetPath + u'/' + qt6CoreName, errorMessage)) { std::wcerr << "Warning: " << *errorMessage << '\n'; @@ -1559,44 +1770,39 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString> } // optPlugins // Update Quick imports - const bool usesQuick1 = result.deployedQtLibraries.test(QtDeclarativeModule); // Do not be fooled by QtWebKit.dll depending on Quick into always installing Quick imports // for WebKit1-applications. Check direct dependency only. - if (options.quickImports && (usesQuick1 || usesQml2)) { - if (usesQml2) { - for (const QmlImportScanResult::Module &module : std::as_const(qmlScanResult.modules)) { - const QString installPath = module.installPath(options.directory); - if (optVerboseLevel > 1) - std::wcout << "Installing: '" << module.name - << "' from " << module.sourcePath << " to " - << QDir::toNativeSeparators(installPath) << '\n'; - if (installPath != options.directory && !createDirectory(installPath, errorMessage)) - return result; - unsigned updateFileFlags = options.updateFileFlags | SkipQmlDesignerSpecificsDirectories; - unsigned qmlDirectoryFileFlags = 0; - if (options.deployPdb) - qmlDirectoryFileFlags |= QmlDirectoryFileEntryFunction::DeployPdb; - if (!updateFile(module.sourcePath, QmlDirectoryFileEntryFunction(options.platform, debugMatchMode, qmlDirectoryFileFlags), - installPath, updateFileFlags, options.json, errorMessage)) { - return result; - } - } - } // Quick 2 - if (usesQuick1) { - const QString quick1ImportPath = - qtpathsVariables.value(QStringLiteral("QT_INSTALL_IMPORTS")); - const QmlDirectoryFileEntryFunction qmlFileEntryFunction(options.platform, debugMatchMode, options.deployPdb ? QmlDirectoryFileEntryFunction::DeployPdb : 0); - QStringList quick1Imports(QStringLiteral("Qt")); - for (const QString &quick1Import : std::as_const(quick1Imports)) { - const QString sourceFile = quick1ImportPath + slash + quick1Import; - if (!updateFile(sourceFile, qmlFileEntryFunction, options.directory, options.updateFileFlags, options.json, errorMessage)) - return result; + if (options.quickImports && usesQml2) { + const QString targetPath = options.qmlDirectory.isEmpty() + ? options.directory + QStringLiteral("/qml") + : options.qmlDirectory; + if (!createDirectory(targetPath, errorMessage, options.dryRun)) + return result; + for (const QmlImportScanResult::Module &module : std::as_const(qmlScanResult.modules)) { + const QString installPath = module.installPath(targetPath); + if (optVerboseLevel > 1) + std::wcout << "Installing: '" << module.name + << "' from " << module.sourcePath << " to " + << QDir::toNativeSeparators(installPath) << '\n'; + if (installPath != targetPath && !createDirectory(installPath, errorMessage, options.dryRun)) + return result; + unsigned updateFileFlags = options.updateFileFlags + | SkipQmlDesignerSpecificsDirectories; + unsigned qmlDirectoryFileFlags = 0; + if (options.deployPdb) + qmlDirectoryFileFlags |= QmlDirectoryFileEntryFunction::DeployPdb; + if (!updateFile(module.sourcePath, QmlDirectoryFileEntryFunction(module.sourcePath, + options.platform, + debugMatchMode, + qmlDirectoryFileFlags), + installPath, updateFileFlags, options.json, errorMessage)) { + return result; } - } // Quick 1 + } } // optQuickImports if (options.translations) { - if (!options.dryRun && !createDirectory(options.translationsDirectory, errorMessage)) + if (!createDirectory(options.translationsDirectory, errorMessage, options.dryRun)) return result; if (!deployTranslations(qtpathsVariables.value(QStringLiteral("QT_INSTALL_TRANSLATIONS")), result.deployedQtLibraries, options.translationsDirectory, options, @@ -1610,7 +1816,8 @@ static DeployResult deploy(const Options &options, const QMap<QString, QString> } static bool deployWebProcess(const QMap<QString, QString> &qtpathsVariables, const char *binaryName, - const Options &sourceOptions, QString *errorMessage) + const PluginInformation &pluginInfo, const Options &sourceOptions, + QString *errorMessage) { // Copy the web process and its dependencies const QString webProcess = webProcessBinary(binaryName, sourceOptions.platform); @@ -1622,29 +1829,32 @@ static bool deployWebProcess(const QMap<QString, QString> &qtpathsVariables, con options.binaries.append(options.directory + u'/' + webProcess); options.quickImports = false; options.translations = false; - return deploy(options, qtpathsVariables, errorMessage); + return deploy(options, qtpathsVariables, pluginInfo, errorMessage); } static bool deployWebEngineCore(const QMap<QString, QString> &qtpathsVariables, - const Options &options, bool isDebug, QString *errorMessage) + const PluginInformation &pluginInfo, const Options &options, + bool isDebug, QString *errorMessage) { - static const char *installDataFiles[] = {"icudtl.dat", - "qtwebengine_devtools_resources.pak", - "qtwebengine_resources.pak", - "qtwebengine_resources_100p.pak", - "qtwebengine_resources_200p.pak"}; + static const char *installDataFiles[] = { "icudtl.dat", + "qtwebengine_devtools_resources.pak", + "qtwebengine_resources.pak", + "qtwebengine_resources_100p.pak", + "qtwebengine_resources_200p.pak", + isDebug ? "v8_context_snapshot.debug.bin" + : "v8_context_snapshot.bin" }; QByteArray webEngineProcessName(webEngineProcessC); if (isDebug && platformHasDebugSuffix(options.platform)) webEngineProcessName.append('d'); if (optVerboseLevel) std::wcout << "Deploying: " << webEngineProcessName.constData() << "...\n"; - if (!deployWebProcess(qtpathsVariables, webEngineProcessName, options, errorMessage)) + if (!deployWebProcess(qtpathsVariables, webEngineProcessName, pluginInfo, options, errorMessage)) return false; const QString resourcesSubDir = QStringLiteral("/resources"); const QString resourcesSourceDir = qtpathsVariables.value(QStringLiteral("QT_INSTALL_DATA")) + resourcesSubDir + u'/'; const QString resourcesTargetDir(options.directory + resourcesSubDir); - if (!createDirectory(resourcesTargetDir, errorMessage)) + if (!createDirectory(resourcesTargetDir, errorMessage, options.dryRun)) return false; for (auto installDataFile : installDataFiles) { if (!updateFile(resourcesSourceDir + QLatin1StringView(installDataFile), @@ -1661,7 +1871,7 @@ static bool deployWebEngineCore(const QMap<QString, QString> &qtpathsVariables, } if (options.translations) { // Copy the whole translations directory. - return createDirectory(options.translationsDirectory, errorMessage) + return createDirectory(options.translationsDirectory, errorMessage, options.dryRun) && updateFile(translations.absoluteFilePath(), options.translationsDirectory, options.updateFileFlags, options.json, errorMessage); } @@ -1674,7 +1884,7 @@ static bool deployWebEngineCore(const QMap<QString, QString> &qtpathsVariables, } const QString webEngineTranslationsDir = options.translationsDirectory + u'/' + translations.fileName(); - if (!createDirectory(webEngineTranslationsDir, errorMessage)) + if (!createDirectory(webEngineTranslationsDir, errorMessage, options.dryRun)) return false; return updateFile(enUSpak.absoluteFilePath(), webEngineTranslationsDir, options.updateFileFlags, options.json, errorMessage); @@ -1700,55 +1910,105 @@ int main(int argc, char **argv) Options options; QString errorMessage; - { // Command line - QCommandLineParser parser; - QString errorMessage; - const int result = parseArguments(QCoreApplication::arguments(), &parser, &options, &errorMessage); - if (result & CommandLineParseError) - std::wcerr << errorMessage << "\n\n"; - if (result & CommandLineParseHelpRequested) - std::fputs(qPrintable(helpText(parser)), stdout); - if (result & CommandLineParseError) + // Early parse the --qmake and --qtpaths options, because they are needed to determine the + // options that select/deselect Qt modules. + { + int result = parseEarlyArguments(QCoreApplication::arguments(), &options, &errorMessage); + if (result & CommandLineParseError) { + std::wcerr << "Error: " << errorMessage << "\n"; return 1; - if (result & CommandLineParseHelpRequested) - return 0; + } } const QMap<QString, QString> qtpathsVariables = queryQtPaths(options.qtpathsBinary, &errorMessage); const QString xSpec = qtpathsVariables.value(QStringLiteral("QMAKE_XSPEC")); - options.platform = platformFromMkSpec(xSpec); - if (qtpathsVariables.isEmpty() || xSpec.isEmpty() || !qtpathsVariables.contains(QStringLiteral("QT_INSTALL_BINS"))) { std::wcerr << "Unable to query qtpaths: " << errorMessage << '\n'; return 1; } + options.platform = platformFromMkSpec(xSpec); + // We are on MSVC and not crosscompiling. We need the host arch + if (options.platform == WindowsDesktopMsvc) { + SYSTEM_INFO si; + GetSystemInfo(&si); + switch (si.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_INTEL: + case PROCESSOR_ARCHITECTURE_IA64: + case PROCESSOR_ARCHITECTURE_AMD64: + options.platform |= IntelBased; + break; + case PROCESSOR_ARCHITECTURE_ARM: + case PROCESSOR_ARCHITECTURE_ARM64: + options.platform |= ArmBased; + break; + default: + options.platform = UnknownPlatform; + } + } if (options.platform == UnknownPlatform) { std::wcerr << "Unsupported platform " << xSpec << '\n'; return 1; } + // Read the Qt module information from the Qt installation directory. + const QString modulesDir + = qtpathsVariables.value(QLatin1String("QT_INSTALL_ARCHDATA")) + + QLatin1String("/modules"); + const QString translationsDir + = qtpathsVariables.value(QLatin1String("QT_INSTALL_TRANSLATIONS")); + if (!qtModuleEntries.populate(modulesDir, translationsDir, optVerboseLevel > 1, + &errorMessage)) { + std::wcerr << "Error: " << errorMessage << "\n"; + return 1; + } + assignKnownModuleIds(); + + // Read the Qt plugin types information from the Qt installation directory. + PluginInformation pluginInfo{}; + pluginInfo.generateAvailablePlugins(qtpathsVariables, options.platform); + + // Parse the full command line. + { + QCommandLineParser parser; + QString errorMessage; + const int result = parseArguments(QCoreApplication::arguments(), &parser, &options, &errorMessage); + if (result & CommandLineParseError) + std::wcerr << errorMessage << "\n\n"; + if (result & CommandLineVersionRequested) { + std::fputs(QT_VERSION_STR "\n", stdout); + return 0; + } + if (result & CommandLineParseHelpRequested) + std::fputs(qPrintable(helpText(parser, pluginInfo)), stdout); + if (result & CommandLineParseError) + return 1; + if (result & CommandLineParseHelpRequested) + return 0; + } + // Create directories - if (!createDirectory(options.directory, &errorMessage)) { + if (!createDirectory(options.directory, &errorMessage, options.dryRun)) { std::wcerr << errorMessage << '\n'; return 1; } if (!options.libraryDirectory.isEmpty() && options.libraryDirectory != options.directory - && !createDirectory(options.libraryDirectory, &errorMessage)) { + && !createDirectory(options.libraryDirectory, &errorMessage, options.dryRun)) { std::wcerr << errorMessage << '\n'; return 1; } - const DeployResult result = deploy(options, qtpathsVariables, &errorMessage); + const DeployResult result = deploy(options, qtpathsVariables, pluginInfo, &errorMessage); if (!result) { std::wcerr << errorMessage << '\n'; return 1; } - if (result.deployedQtLibraries.test(QtWebEngineCoreModule)) { - if (!deployWebEngineCore(qtpathsVariables, options, result.isDebug, &errorMessage)) { + if (result.deployedQtLibraries.test(QtWebEngineCoreModuleId)) { + if (!deployWebEngineCore(qtpathsVariables, pluginInfo, options, result.isDebug, + &errorMessage)) { std::wcerr << errorMessage << '\n'; return 1; } diff --git a/src/tools/windeployqt/qmlutils.cpp b/src/tools/windeployqt/qmlutils.cpp index 5104af0194..a7e63e7470 100644 --- a/src/tools/windeployqt/qmlutils.cpp +++ b/src/tools/windeployqt/qmlutils.cpp @@ -67,7 +67,8 @@ static void findFileRecursion(const QDir &directory, Platform platform, const QFileInfoList &subDirs = directory.entryInfoList(QStringList(), QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); for (const QFileInfo &subDirFi : subDirs) { QDir subDirectory(subDirFi.absoluteFilePath()); - if (subDirectory.isReadable()) + // Don't enter other QML modules when recursing! + if (subDirectory.isReadable() && !subDirectory.exists(QStringLiteral("qmldir"))) findFileRecursion(subDirectory, platform, debugMatchMode, matches); } } diff --git a/src/tools/windeployqt/qtmoduleinfo.cpp b/src/tools/windeployqt/qtmoduleinfo.cpp new file mode 100644 index 0000000000..b928a64478 --- /dev/null +++ b/src/tools/windeployqt/qtmoduleinfo.cpp @@ -0,0 +1,183 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtmoduleinfo.h" +#include "utils.h" + +#include <QDirListing> +#include <QJsonDocument> +#include <QJsonArray> +#include <QDebug> + +#include <iostream> +#include <algorithm> +#include <unordered_map> + +using namespace Qt::StringLiterals; + +static QStringList toStringList(const QJsonArray &jsonArray) +{ + QStringList result; + for (const auto &item : jsonArray) { + if (item.isString()) + result.append(item.toString()); + } + return result; +} + +struct TranslationCatalog +{ + QString name; + QStringList repositories; + QStringList modules; +}; + +using TranslationCatalogs = std::vector<TranslationCatalog>; + +static TranslationCatalogs readTranslationsCatalogs(const QString &translationsDir, + bool verbose, + QString *errorString) +{ + QFile file(translationsDir + QLatin1String("/catalogs.json")); + if (verbose) { + std::wcerr << "Trying to read translation catalogs from \"" + << qUtf8Printable(file.fileName()) << "\".\n"; + } + if (!file.open(QIODevice::ReadOnly)) { + *errorString = QLatin1String("Cannot open ") + file.fileName(); + return {}; + } + + QJsonParseError jsonParseError; + QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &jsonParseError); + if (jsonParseError.error != QJsonParseError::NoError) { + *errorString = jsonParseError.errorString(); + return {}; + } + + if (!document.isArray()) { + *errorString = QLatin1String("Expected an array as root element of ") + file.fileName(); + return {}; + } + + TranslationCatalogs catalogs; + for (const QJsonValueRef &item : document.array()) { + TranslationCatalog catalog; + catalog.name = item[QLatin1String("name")].toString(); + catalog.repositories = toStringList(item[QLatin1String("repositories")].toArray()); + catalog.modules = toStringList(item[QLatin1String("modules")].toArray()); + if (verbose) + std::wcerr << "Found catalog \"" << qUtf8Printable(catalog.name) << "\".\n"; + catalogs.emplace_back(std::move(catalog)); + } + + return catalogs; +} + +static QtModule moduleFromJsonFile(const QString &filePath, QString *errorString) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + *errorString = QLatin1String("Cannot open ") + file.fileName(); + return {}; + } + + QJsonParseError jsonParseError; + QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &jsonParseError); + if (jsonParseError.error != QJsonParseError::NoError) { + *errorString = jsonParseError.errorString(); + return {}; + } + + if (!document.isObject()) { + *errorString = QLatin1String("Expected an object as root element of ") + file.fileName(); + return {}; + } + + const QJsonObject obj = document.object(); + QtModule module; + module.name = "Qt6"_L1 + obj[QLatin1String("name")].toString(); + module.repository = obj[QLatin1String("repository")].toString(); + module.internal = obj[QLatin1String("internal")].toBool(); + module.pluginTypes = toStringList(obj[QLatin1String("plugin_types")].toArray()); + return module; +} + +static void dump(const QtModule &module) +{ + std::wcerr << "Found module \"" << qUtf8Printable(module.name) << "\".\n"; + if (!module.pluginTypes.isEmpty()) + qDebug().nospace() << " plugin types: " << module.pluginTypes; + if (!module.translationCatalog.isEmpty()) + qDebug().nospace() << " translation catalog: "<< module.translationCatalog; +} + +bool QtModuleInfoStore::populate(const QString &modulesDir, const QString &translationsDir, + bool verbose, QString *errorString) +{ + const TranslationCatalogs catalogs = readTranslationsCatalogs(translationsDir, verbose, + errorString); + if (!errorString->isEmpty()) { + std::wcerr << "Warning: Translations will not be available due to the following error." + << std::endl << *errorString << std::endl; + errorString->clear(); + } + std::unordered_map<QString, QString> moduleToCatalogMap; + std::unordered_map<QString, QString> repositoryToCatalogMap; + for (const TranslationCatalog &catalog : catalogs) { + for (const QString &module : catalog.modules) { + moduleToCatalogMap.insert(std::make_pair(module, catalog.name)); + } + for (const QString &repository : catalog.repositories) { + repositoryToCatalogMap.insert(std::make_pair(repository, catalog.name)); + } + } + + // Read modules, and assign a bit as ID. + for (const auto &dirEntry : QDirListing(modulesDir, {u"*.json"_s}, QDir::Files)) { + QtModule module = moduleFromJsonFile(dirEntry.filePath(), errorString); + if (!errorString->isEmpty()) + return false; + if (module.internal && module.name.endsWith(QStringLiteral("Private"))) + module.name.chop(7); + module.id = modules.size(); + if (module.id == QtModule::InvalidId) { + *errorString = "Internal Error: too many modules for ModuleBitset to hold."_L1; + return false; + } + + { + auto it = moduleToCatalogMap.find(module.name); + if (it != moduleToCatalogMap.end()) + module.translationCatalog = it->second; + } + if (module.translationCatalog.isEmpty()) { + auto it = repositoryToCatalogMap.find(module.repository); + if (it != repositoryToCatalogMap.end()) + module.translationCatalog = it->second; + } + if (verbose) + dump(module); + modules.emplace_back(std::move(module)); + } + + return true; +} + +const QtModule &QtModuleInfoStore::moduleById(size_t id) const +{ + return modules.at(id); +} + +size_t QtModuleInfoStore::moduleIdForPluginType(const QString &pluginType) const +{ + auto moduleHasPluginType = [&pluginType] (const QtModule &module) { + return module.pluginTypes.contains(pluginType); + }; + + auto it = std::find_if(modules.begin(), modules.end(), moduleHasPluginType); + if (it != modules.end()) + return it->id ; + + return QtModule::InvalidId; +} diff --git a/src/tools/windeployqt/qtmoduleinfo.h b/src/tools/windeployqt/qtmoduleinfo.h new file mode 100644 index 0000000000..b35403a090 --- /dev/null +++ b/src/tools/windeployqt/qtmoduleinfo.h @@ -0,0 +1,51 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QTMODULEINFO_H +#define QTMODULEINFO_H + +#include <QString> +#include <QStringList> + +#include <bitset> +#include <vector> + +constexpr size_t ModuleBitsetSize = 1024; +using ModuleBitset = std::bitset<ModuleBitsetSize>; + +struct QtModule +{ + static constexpr size_t InvalidId = ModuleBitsetSize - 1; + size_t id = InvalidId; + bool internal = false; + QString name; + QString repository; + QStringList pluginTypes; + QString translationCatalog; +}; + +inline bool contains(const ModuleBitset &modules, const QtModule &module) +{ + return modules.test(module.id); +} + +class QtModuleInfoStore +{ +public: + QtModuleInfoStore() = default; + + bool populate(const QString &modulesDir, const QString &translationsDir, bool verbose, + QString *errorString); + + size_t size() const { return modules.size(); } + std::vector<QtModule>::const_iterator begin() const { return modules.begin(); } + std::vector<QtModule>::const_iterator end() const { return modules.end(); } + + const QtModule &moduleById(size_t id) const; + size_t moduleIdForPluginType(const QString &pluginType) const; + +private: + std::vector<QtModule> modules; +}; + +#endif diff --git a/src/tools/windeployqt/qtplugininfo.cpp b/src/tools/windeployqt/qtplugininfo.cpp new file mode 100644 index 0000000000..1deaa35f35 --- /dev/null +++ b/src/tools/windeployqt/qtplugininfo.cpp @@ -0,0 +1,100 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtplugininfo.h" + +#include <QDir> + +static PluginDetection determinePluginLibrary(const QDir &platformPluginDir, const QString &infix) +{ + // Use the platform plugin to determine which dlls are there (release/debug/both) + QString platformReleaseFilter(QStringLiteral("qwindows")); + if (!infix.isEmpty()) + platformReleaseFilter += infix; + QString platformFilter = platformReleaseFilter + u'*'; + platformFilter += sharedLibrarySuffix(); + + const QFileInfoList &dlls = + platformPluginDir.entryInfoList(QStringList(platformFilter), QDir::Files); + if (dlls.size() == 1) { + const QFileInfo dllFi = dlls.first(); + const bool hasDebugDlls = + dllFi.fileName() == QString(platformReleaseFilter + sharedLibrarySuffix()) ? false + : true; + return (hasDebugDlls ? PluginDetection::DebugOnly : PluginDetection::ReleaseOnly); + } else { + return PluginDetection::DebugAndRelease; + } +} + +static QStringList findPluginNames(const QDir &pluginDir, const PluginDetection libraryType, + const Platform &platform) +{ + QString errorMessage{}; + QStringList result{}; + QString filter{}; + filter += u"*"; + filter += sharedLibrarySuffix(); + + const QFileInfoList &dlls = + pluginDir.entryInfoList(QStringList(filter), QDir::Files, QDir::Name); + + for (const QFileInfo &dllFi : dlls) { + QString plugin = dllFi.fileName(); + const int dotIndex = plugin.lastIndexOf(u'.'); + // We don't need the .dll for the name + plugin = plugin.first(dotIndex); + + if (libraryType == PluginDetection::DebugAndRelease) { + bool isDebugDll{}; + if (!readPeExecutable(dllFi.absoluteFilePath(), &errorMessage, 0, 0, &isDebugDll, + (platform == WindowsDesktopMinGW))) { + std::wcerr << "Warning: Unable to read " + << QDir::toNativeSeparators(dllFi.absoluteFilePath()) << ": " + << errorMessage; + } + if (isDebugDll && platformHasDebugSuffix(platform)) + plugin.removeLast(); + } + else if (libraryType == PluginDetection::DebugOnly) + plugin.removeLast(); + + if (!result.contains(plugin)) + result.append(plugin); + } + return result; +} + +bool PluginInformation::isTypeForPlugin(const QString &type, const QString &plugin) const +{ + return m_pluginMap.at(plugin) == type; +} + +void PluginInformation::populatePluginToType(const QDir &pluginDir, const QStringList &plugins) +{ + for (const QString &plugin : plugins) + m_pluginMap.insert({ plugin, pluginDir.dirName() }); +} + +void PluginInformation::generateAvailablePlugins(const QMap<QString, QString> &qtPathsVariables, + const Platform &platform) +{ + const QDir pluginTypesDir(qtPathsVariables.value(QLatin1String("QT_INSTALL_PLUGINS"))); + const QDir platformPluginDir(pluginTypesDir.absolutePath() + QStringLiteral("/platforms")); + const QString infix(qtPathsVariables.value(QLatin1String(qmakeInfixKey))); + const PluginDetection debugDetection = determinePluginLibrary(platformPluginDir, infix); + + const QFileInfoList &pluginTypes = + pluginTypesDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo &pluginType : pluginTypes) { + const QString pluginTypeName = pluginType.baseName(); + m_typeMap.insert({ pluginTypeName, QStringList{} }); + const QStringList plugins = + findPluginNames(pluginType.absoluteFilePath(), debugDetection, platform); + m_typeMap.at(pluginTypeName) = plugins; + populatePluginToType(pluginTypeName, plugins); + } + if (!m_typeMap.size() || !m_pluginMap.size()) + std::wcerr << "Warning: could not parse available plugins properly, plugin " + "inclusion/exclusion options will not work\n"; +} diff --git a/src/tools/windeployqt/qtplugininfo.h b/src/tools/windeployqt/qtplugininfo.h new file mode 100644 index 0000000000..420b2b5e1a --- /dev/null +++ b/src/tools/windeployqt/qtplugininfo.h @@ -0,0 +1,48 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QTPLUGININFO_H +#define QTPLUGININFO_H + +#include "utils.h" + +#include <QString> +#include <QStringList> + +#include <unordered_map> + +enum class PluginDetection +{ + DebugOnly, + ReleaseOnly, + DebugAndRelease +}; + +struct PluginSelections +{ + QStringList disabledPluginTypes; + QStringList enabledPluginTypes; + QStringList excludedPlugins; + QStringList includedPlugins; + bool includeSoftPlugins = false; +}; + +class PluginInformation +{ +public: + PluginInformation() = default; + + bool isTypeForPlugin(const QString &type, const QString &plugin) const; + + void generateAvailablePlugins(const QMap<QString, QString> &qtPathsVariables, + const Platform &platform); + void populatePluginToType(const QDir &pluginDir, const QStringList &plugins); + + const std::unordered_map<QString, QStringList> &typeMap() const { return m_typeMap; } + +private: + std::unordered_map<QString, QStringList> m_typeMap; + std::unordered_map<QString, QString> m_pluginMap; +}; + +#endif diff --git a/src/tools/windeployqt/utils.cpp b/src/tools/windeployqt/utils.cpp index 7f4763a850..5141119254 100644 --- a/src/tools/windeployqt/utils.cpp +++ b/src/tools/windeployqt/utils.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "utils.h" -#include "elfreader.h" #include <QtCore/QString> #include <QtCore/QDebug> @@ -62,7 +61,7 @@ bool createSymbolicLink(const QFileInfo &source, const QString &target, QString return true; } -bool createDirectory(const QString &directory, QString *errorMessage) +bool createDirectory(const QString &directory, QString *errorMessage, bool dryRun) { const QFileInfo fi(directory); if (fi.isDir()) @@ -74,11 +73,13 @@ bool createDirectory(const QString &directory, QString *errorMessage) } if (optVerboseLevel) std::wcout << "Creating " << QDir::toNativeSeparators(directory) << "...\n"; - QDir dir; - if (!dir.mkpath(directory)) { - *errorMessage = QString::fromLatin1("Could not create directory %1."). - arg(QDir::toNativeSeparators(directory)); - return false; + if (!dryRun) { + QDir dir; + if (!dir.mkpath(directory)) { + *errorMessage = QString::fromLatin1("Could not create directory %1.") + .arg(QDir::toNativeSeparators(directory)); + return false; + } } return true; } @@ -93,7 +94,7 @@ QStringList findSharedLibraries(const QDir &directory, Platform platform, nameFilter += u'*'; if (debugMatchMode == MatchDebug && platformHasDebugSuffix(platform)) nameFilter += u'd'; - nameFilter += sharedLibrarySuffix(platform); + nameFilter += sharedLibrarySuffix(); QStringList result; QString errorMessage; const QFileInfoList &dlls = directory.entryInfoList(QStringList(nameFilter), QDir::Files); @@ -422,14 +423,18 @@ const char *qmakeInfixKey = "QT_INFIX"; QMap<QString, QString> queryQtPaths(const QString &qtpathsBinary, QString *errorMessage) { const QString binary = !qtpathsBinary.isEmpty() ? qtpathsBinary : QStringLiteral("qtpaths"); + const QString colonSpace = QStringLiteral(": "); QByteArray stdOut; QByteArray stdErr; unsigned long exitCode = 0; - if (!runProcess(binary, QStringList(QStringLiteral("-query")), QString(), &exitCode, &stdOut, &stdErr, errorMessage)) + if (!runProcess(binary, QStringList(QStringLiteral("-query")), QString(), &exitCode, &stdOut, + &stdErr, errorMessage)) { + *errorMessage = QStringLiteral("Error running binary ") + binary + colonSpace + *errorMessage; return QMap<QString, QString>(); + } if (exitCode) { *errorMessage = binary + QStringLiteral(" returns ") + QString::number(exitCode) - + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr); + + colonSpace + QString::fromLocal8Bit(stdErr); return QMap<QString, QString>(); } const QString output = QString::fromLocal8Bit(stdOut).trimmed().remove(u'\r'); @@ -465,7 +470,7 @@ QMap<QString, QString> queryQtPaths(const QString &qtpathsBinary, QString *error } } else { std::wcerr << "Warning: Unable to read " << QDir::toNativeSeparators(qconfigPriFile.fileName()) - << ": " << qconfigPriFile.errorString()<< '\n'; + << colonSpace << qconfigPriFile.errorString()<< '\n'; } return result; } @@ -551,37 +556,6 @@ bool updateFile(const QString &sourceFileName, const QStringList &nameFilters, return true; } -bool readElfExecutable(const QString &elfExecutableFileName, QString *errorMessage, - QStringList *dependentLibraries, unsigned *wordSize, - bool *isDebug) -{ - ElfReader elfReader(elfExecutableFileName); - const ElfData data = elfReader.readHeaders(); - if (data.sectionHeaders.isEmpty()) { - *errorMessage = QStringLiteral("Unable to read ELF binary \"") - + QDir::toNativeSeparators(elfExecutableFileName) + QStringLiteral("\": ") - + elfReader.errorString(); - return false; - } - if (wordSize) - *wordSize = data.elfclass == Elf_ELFCLASS64 ? 64 : 32; - if (dependentLibraries) { - dependentLibraries->clear(); - const QList<QByteArray> libs = elfReader.dependencies(); - if (libs.isEmpty()) { - *errorMessage = QStringLiteral("Unable to read dependenices of ELF binary \"") - + QDir::toNativeSeparators(elfExecutableFileName) + QStringLiteral("\": ") - + elfReader.errorString(); - return false; - } - for (const QByteArray &l : libs) - dependentLibraries->push_back(QString::fromLocal8Bit(l)); - } - if (isDebug) - *isDebug = data.symbolsType != UnknownSymbols && data.symbolsType != NoSymbols; - return true; -} - #ifdef Q_OS_WIN static inline QString stringFromRvaPtr(const void *rvaPtr) @@ -703,13 +677,23 @@ static inline MsvcDebugRuntimeResult checkMsvcDebugRuntime(const QStringList &de qsizetype pos = 0; if (lib.startsWith("MSVCR"_L1, Qt::CaseInsensitive) || lib.startsWith("MSVCP"_L1, Qt::CaseInsensitive) - || lib.startsWith("VCRUNTIME"_L1, Qt::CaseInsensitive)) { + || lib.startsWith("VCRUNTIME"_L1, Qt::CaseInsensitive) + || lib.startsWith("VCCORLIB"_L1, Qt::CaseInsensitive) + || lib.startsWith("CONCRT"_L1, Qt::CaseInsensitive) + || lib.startsWith("UCRTBASE"_L1, Qt::CaseInsensitive)) { qsizetype lastDotPos = lib.lastIndexOf(u'.'); pos = -1 == lastDotPos ? 0 : lastDotPos - 1; } - if (pos > 0 && lib.contains("_app"_L1, Qt::CaseInsensitive)) - pos -= 4; + if (pos > 0) { + const auto removeExtraSuffix = [&lib, &pos](const QString &suffix) -> void { + if (lib.contains(suffix, Qt::CaseInsensitive)) + pos -= suffix.size(); + }; + removeExtraSuffix("_app"_L1); + removeExtraSuffix("_atomic_wait"_L1); + removeExtraSuffix("_codecvt_ids"_L1); + } if (pos) return lib.at(pos).toLower() == u'd' ? MsvcDebugRuntime : MsvcReleaseRuntime; @@ -718,28 +702,43 @@ static inline MsvcDebugRuntimeResult checkMsvcDebugRuntime(const QStringList &de } template <class ImageNtHeader> +inline QStringList determineDependentLibs(const ImageNtHeader *nth, const void *fileMemory, + QString *errorMessage) +{ + return readImportSections(nth, fileMemory, errorMessage); +} + +template <class ImageNtHeader> +inline bool determineDebug(const ImageNtHeader *nth, const void *fileMemory, + QStringList *dependentLibrariesIn, QString *errorMessage) +{ + if (nth->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED) + return false; + + const QStringList dependentLibraries = dependentLibrariesIn != nullptr ? + *dependentLibrariesIn : + determineDependentLibs(nth, fileMemory, errorMessage); + + const bool hasDebugEntry = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; + // When an MSVC debug entry is present, check whether the debug runtime + // is actually used to detect -release / -force-debug-info builds. + const MsvcDebugRuntimeResult msvcrt = checkMsvcDebugRuntime(dependentLibraries); + if (msvcrt == NoMsvcRuntime) + return hasDebugEntry; + else + return hasDebugEntry && msvcrt == MsvcDebugRuntime; +} + +template <class ImageNtHeader> inline void determineDebugAndDependentLibs(const ImageNtHeader *nth, const void *fileMemory, - bool isMinGW, QStringList *dependentLibrariesIn, bool *isDebugIn, QString *errorMessage) { - const bool hasDebugEntry = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; - QStringList dependentLibraries; - if (dependentLibrariesIn || (isDebugIn != nullptr && hasDebugEntry && !isMinGW)) - dependentLibraries = readImportSections(nth, fileMemory, errorMessage); - if (dependentLibrariesIn) - *dependentLibrariesIn = dependentLibraries; - if (isDebugIn != nullptr) { - if (isMinGW) { - // Use logic that's used e.g. in objdump / pfd library - *isDebugIn = !(nth->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED); - } else { - // When an MSVC debug entry is present, check whether the debug runtime - // is actually used to detect -release / -force-debug-info builds. - *isDebugIn = hasDebugEntry && checkMsvcDebugRuntime(dependentLibraries) != MsvcReleaseRuntime; - } - } + *dependentLibrariesIn = determineDependentLibs(nth, fileMemory, errorMessage); + + if (isDebugIn) + *isDebugIn = determineDebug(nth, fileMemory, dependentLibrariesIn, errorMessage); } // Read a PE executable and determine dependent libraries, word size @@ -793,10 +792,10 @@ bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage *wordSizeIn = wordSize; if (wordSize == 32) { determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(ntHeaders), - fileMemory, isMinGW, dependentLibrariesIn, isDebugIn, errorMessage); + fileMemory, dependentLibrariesIn, isDebugIn, errorMessage); } else { determineDebugAndDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(ntHeaders), - fileMemory, isMinGW, dependentLibrariesIn, isDebugIn, errorMessage); + fileMemory, dependentLibrariesIn, isDebugIn, errorMessage); } if (machineArchIn) @@ -879,6 +878,53 @@ QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wor return QString(); } +QStringList findDxc(Platform platform, const QString &qtBinDir, unsigned wordSize) +{ + QStringList results; + const QString kitDir = QString::fromLocal8Bit(qgetenv("WindowsSdkDir")); + const QString suffix = QLatin1StringView(windowsSharedLibrarySuffix); + for (QString prefix : { QStringLiteral("dxcompiler"), QStringLiteral("dxil") }) { + QString name = prefix + suffix; + if (!kitDir.isEmpty()) { + QString redistDirPath = QDir::cleanPath(kitDir) + QStringLiteral("/Redist/D3D/"); + if (platform.testFlag(ArmBased)) { + redistDirPath += wordSize == 32 ? QStringLiteral("arm") : QStringLiteral("arm64"); + } else { + redistDirPath += wordSize == 32 ? QStringLiteral("x86") : QStringLiteral("x64"); + } + QDir redistDir(redistDirPath); + if (redistDir.exists()) { + const QFileInfoList files = redistDir.entryInfoList(QStringList(prefix + u'*' + suffix), QDir::Files); + if (!files.isEmpty()) { + results.append(files.front().absoluteFilePath()); + continue; + } + } + } + // Check the bin directory of the Qt SDK (in case it is shadowed by the + // Windows system directory in PATH). + const QFileInfo fi(qtBinDir + u'/' + name); + if (fi.isFile()) { + results.append(fi.absoluteFilePath()); + continue; + } + // Try to find it in the PATH (e.g. the Vulkan SDK ships these, even if Windows itself doesn't). + if (platform.testFlag(IntelBased)) { + QString errorMessage; + unsigned detectedWordSize; + const QString dll = findInPath(name); + if (!dll.isEmpty() + && readPeExecutable(dll, &errorMessage, 0, &detectedWordSize, 0) + && detectedWordSize == wordSize) + { + results.append(dll); + continue; + } + } + } + return results; +} + #else // Q_OS_WIN bool readPeExecutable(const QString &, QString *errorMessage, @@ -893,6 +939,11 @@ QString findD3dCompiler(Platform, const QString &, unsigned) return QString(); } +QStringList findDxc(Platform, const QString &, unsigned) +{ + return QStringList(); +} + #endif // !Q_OS_WIN // Search for "qt_prfxpath=xxxx" in \a path, and replace it with "qt_prfxpath=." diff --git a/src/tools/windeployqt/utils.h b/src/tools/windeployqt/utils.h index a9ee509d7a..fb3ba0b40b 100644 --- a/src/tools/windeployqt/utils.h +++ b/src/tools/windeployqt/utils.h @@ -20,7 +20,6 @@ QT_BEGIN_NAMESPACE enum PlatformFlag { // OS WindowsBased = 0x00001, - UnixBased = 0x00002, // CPU IntelBased = 0x00010, ArmBased = 0x00020, @@ -30,11 +29,12 @@ enum PlatformFlag { ClangMsvc = 0x00400, ClangMinGW = 0x00800, // Platforms - WindowsDesktopMsvc = WindowsBased + IntelBased + Msvc, + WindowsDesktopMsvc = WindowsBased + Msvc, + WindowsDesktopMsvcIntel = WindowsDesktopMsvc + IntelBased, + WindowsDesktopMsvcArm = WindowsDesktopMsvc + ArmBased, WindowsDesktopMinGW = WindowsBased + IntelBased + MinGW, WindowsDesktopClangMsvc = WindowsBased + IntelBased + ClangMsvc, WindowsDesktopClangMinGW = WindowsBased + IntelBased + ClangMinGW, - Unix = UnixBased, UnknownPlatform }; @@ -68,7 +68,7 @@ inline std::wostream &operator<<(std::wostream &str, const QString &s) // Container class for JSON output class JsonOutput { - using SourceTargetMapping = QPair<QString, QString>; + using SourceTargetMapping = std::pair<QString, QString>; using SourceTargetMappings = QList<SourceTargetMapping>; public: @@ -137,13 +137,12 @@ inline QString normalizeFileName(const QString &name) { return name; } #endif // !Q_OS_WIN static const char windowsSharedLibrarySuffix[] = ".dll"; -static const char unixSharedLibrarySuffix[] = ".so"; -inline QString sharedLibrarySuffix(Platform platform) { return QLatin1StringView((platform & WindowsBased) ? windowsSharedLibrarySuffix : unixSharedLibrarySuffix); } +inline QString sharedLibrarySuffix() { return QLatin1StringView(windowsSharedLibrarySuffix); } bool isBuildDirectory(Platform platform, const QString &dirName); bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage); -bool createDirectory(const QString &directory, QString *errorMessage); +bool createDirectory(const QString &directory, QString *errorMessage, bool dryRun); QString findInPath(const QString &file); extern const char *qmakeInfixKey; // Fake key containing the libinfix @@ -170,19 +169,6 @@ bool runProcess(const QString &binary, const QStringList &args, bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage, QStringList *dependentLibraries = 0, unsigned *wordSize = 0, bool *isDebug = 0, bool isMinGW = false, unsigned short *machineArch = nullptr); -bool readElfExecutable(const QString &elfExecutableFileName, QString *errorMessage, - QStringList *dependentLibraries = 0, unsigned *wordSize = 0, - bool *isDebug = 0); - -inline bool readExecutable(const QString &executableFileName, Platform platform, - QString *errorMessage, QStringList *dependentLibraries = 0, - unsigned *wordSize = 0, bool *isDebug = 0, unsigned short *machineArch = nullptr) -{ - return platform == Unix ? - readElfExecutable(executableFileName, errorMessage, dependentLibraries, wordSize, isDebug) : - readPeExecutable(executableFileName, errorMessage, dependentLibraries, wordSize, isDebug, - (platform == WindowsDesktopMinGW), machineArch); -} #ifdef Q_OS_WIN # if !defined(IMAGE_FILE_MACHINE_ARM64) @@ -193,14 +179,15 @@ QString getArchString (unsigned short machineArch); // Return dependent modules of executable files. -inline QStringList findDependentLibraries(const QString &executableFileName, Platform platform, QString *errorMessage) +inline QStringList findDependentLibraries(const QString &executableFileName, QString *errorMessage) { QStringList result; - readExecutable(executableFileName, platform, errorMessage, &result); + readPeExecutable(executableFileName, errorMessage, &result); return result; } QString findD3dCompiler(Platform platform, const QString &qtBinDir, unsigned wordSize); +QStringList findDxc(Platform platform, const QString &qtBinDir, unsigned wordSize); bool patchQtCore(const QString &path, QString *errorMessage); |