diff options
author | Friedemann Kleint <Friedemann.Kleint@digia.com> | 2013-08-26 16:09:06 +0200 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-09-30 16:06:39 +0200 |
commit | c5262bb93a040bd8519c70a8f9adc08549bb49d4 (patch) | |
tree | 536f1487f4e9c6b122a10ea6c1c6bf326d22ba53 | |
parent | 33d9822459a654bf72078b88f6b4466cfcdd609c (diff) | |
download | qttools-c5262bb93a040bd8519c70a8f9adc08549bb49d4.tar.gz |
Long live windeployqt!
Add windeployqt for deploying Windows/Windows Runtime applications.
Change-Id: Ia6ca4af13a93fdc75ef6fe4f794cbe228533e85f
Reviewed-by: Oliver Wolff <oliver.wolff@digia.com>
Reviewed-by: Maurice Kalinowski <maurice.kalinowski@digia.com>
-rw-r--r-- | src/src.pro | 2 | ||||
-rw-r--r-- | src/windeployqt/elfreader.cpp | 450 | ||||
-rw-r--r-- | src/windeployqt/elfreader.h | 189 | ||||
-rw-r--r-- | src/windeployqt/main.cpp | 945 | ||||
-rw-r--r-- | src/windeployqt/utils.cpp | 805 | ||||
-rw-r--r-- | src/windeployqt/utils.h | 252 | ||||
-rw-r--r-- | src/windeployqt/windeployqt.pro | 14 |
7 files changed, 2657 insertions, 0 deletions
diff --git a/src/src.pro b/src/src.pro index 0a88949b8..6c78146bb 100644 --- a/src/src.pro +++ b/src/src.pro @@ -27,6 +27,8 @@ android { qtHaveModule(dbus): SUBDIRS += qdbus +win32|winrt:SUBDIRS += windeployqt + qtNomakeTools( \ pixeltool \ qtconfig \ diff --git a/src/windeployqt/elfreader.cpp b/src/windeployqt/elfreader.cpp new file mode 100644 index 000000000..bfd144179 --- /dev/null +++ b/src/windeployqt/elfreader.cpp @@ -0,0 +1,450 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "elfreader.h" + +#include <QDir> + +QT_BEGIN_NAMESPACE + +/* 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 = 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 = file.size(); + ustart = file.map(0, fdlen); + if (ustart == 0) { + // Try reading the data into memory instead. + raw = file.readAll(); + start = raw.constData(); + fdlen = 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(QLatin1String("wrong cpu architecture")); + // 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(QLatin1String("shstrtab section header seems to be at %1")) +// .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, + 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, 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; + + foreach (const ElfSectionHeader &eh, readHeaders().sectionHeaders) { + 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); + } + } + return result; +} + +QT_END_NAMESPACE diff --git a/src/windeployqt/elfreader.h b/src/windeployqt/elfreader.h new file mode 100644 index 000000000..0b071a321 --- /dev/null +++ b/src/windeployqt/elfreader.h @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef ELFREADER_H +#define ELFREADER_H + +#include <QtCore/QtEndian> +#include <QtCore/QString> +#include <QtCore/QVector> + +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 symols 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; + QVector<ElfSectionHeader> sectionHeaders; + QVector<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/windeployqt/main.cpp b/src/windeployqt/main.cpp new file mode 100644 index 000000000..e49603697 --- /dev/null +++ b/src/windeployqt/main.cpp @@ -0,0 +1,945 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "utils.h" + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QCoreApplication> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonArray> +#include <QtCore/QCommandLineParser> +#include <QtCore/QCommandLineOption> +#include <QtCore/QVector> + +#include <cstdio> + +QT_BEGIN_NAMESPACE + +enum QtModule { + QtCoreModule = 0x1, + QtGuiModule = 0x2, + QtSqlModule = 0x4, + QtNetworkModule = 0x8, + QtMultimediaModule = 0x10, + QtMultimediaWidgetsModule = 0x20, + QtOpenGlModule = 0x40, + QtPrintSupportModule = 0x80, + QtDeclarativeModule = 0x100, + QtQmlModule = 0x200, + QtQuickModule = 0x400, + QtQuickParticlesModule = 0x800, + QtScriptModule = 0x1000, + QtSvgModule = 0x2000, + QtXmlModule = 0x4000, + QtXmlPatternsModule = 0x8000, + QtHelpModule = 0x10000, + QtSensorsModule = 0x20000, + QtWidgetsModule = 0x40000, + QtWebKitModule = 0x80000, + QtWebKitWidgetsModule = 0x100000 +}; + +struct QtModuleEntry { + unsigned module; + const char *option; + const char *libraryName; + const char *translation; +}; + +QtModuleEntry qtModuleEntries[] = { + { QtCoreModule, "core", "Qt5Core", "qtbase" }, + { QtGuiModule, "gui", "Qt5Gui", "qtbase" }, + { QtQmlModule, "qml", "Qt5Qml", "qtdeclarative" }, + { QtQuickModule, "quick", "Qt5Quick", "qtdeclarative" }, + { QtWidgetsModule, "widgets", "Qt5Widgets", "qtbase" }, + { QtOpenGlModule, "opengl", "Qt5OpenGL", 0 }, + { QtPrintSupportModule, "printsupport", "Qt5PrintSupport", 0 }, + { QtNetworkModule, "network", "Qt5Network", "qtbase" }, + { QtSqlModule, "sql", "Qt5Sql", "qtbase" }, + { QtMultimediaModule, "multimedia", "Qt5Multimedia", "qtmultimedia" }, + { QtMultimediaWidgetsModule, "multimediawidgets", "Qt5MultimediaWidgets", "qtmultimedia" }, + { QtQuickParticlesModule, "quickparticles", "Qt5QuickParticles", 0 }, + { QtXmlPatternsModule, "xmlpatterns", "Qt5XmlPatterns", "qtxmlpatterns" }, + { QtHelpModule, "help", "Qt5Help", "qt_help" }, + { QtSensorsModule, "sensors", "Qt5Sensors", 0 }, + { QtSvgModule, "svg", "Qt5Svg", 0 }, + { QtWebKitModule, "webkit", "Qt5WebKit", 0 }, + { QtWebKitWidgetsModule, "webkitwidgets", "Qt5WebKitWidgets", 0 }, + { QtScriptModule, "script", "Qt5Script", "qtscript" }, + { QtXmlModule, "xml", "Qt5Xml", 0 }, + { QtDeclarativeModule, "declarative", "Qt5Declarative", "qtquick1" }, +}; + +static const char webProcessC[] = "QtWebProcess"; + +static inline QString webProcessBinary(Platform p) +{ + const QString webProcess = QLatin1String(webProcessC); + return (p & WindowsBased) ? webProcess + QStringLiteral(".exe") : webProcess; +} + +static QByteArray formatQtModules(unsigned mask, bool option = false) +{ + QByteArray result; + const size_t qtModulesCount = sizeof(qtModuleEntries)/sizeof(QtModuleEntry); + for (size_t i = 0; i < qtModulesCount; ++i) { + if (mask & qtModuleEntries[i].module) { + if (!result.isEmpty()) + result.append(' '); + result.append(option ? qtModuleEntries[i].option : qtModuleEntries[i].libraryName); + } + } + return result; +} + +static Platform platformFromMkSpec(const QString &xSpec) +{ + if (xSpec == QLatin1String("linux-g++")) + return Unix; + if (xSpec.startsWith(QLatin1String("win32-"))) + return Windows; + if (xSpec.startsWith(QLatin1String("winrt-x"))) + return WinRtIntel; + if (xSpec.startsWith(QLatin1String("winrt-arm"))) + return WinRtArm; + if (xSpec.startsWith(QLatin1String("winphone-x"))) + return WinPhoneIntel; + if (xSpec.startsWith(QLatin1String("winphone-arm"))) + return WinPhoneArm; + return UnknownPlatform; +} + +bool optHelp = false; +int optWebKit2 = 0; + +// Container class for JSON output +class JsonOutput { +public: + void addFile(const QString &source, const QString &target) + { + QJsonObject object; + object.insert(QStringLiteral("source"), QDir::toNativeSeparators(source)); + object.insert(QStringLiteral("target"), QDir::toNativeSeparators(target)); + m_files.append(object); + } + QByteArray toJson() const + { + QJsonObject document; + document.insert(QStringLiteral("files"), m_files); + return QJsonDocument(document).toJson(); + } +private: + QJsonArray m_files; +}; + +struct Options { + Options() : plugins(true), libraries(true), quickImports(true), translations(true) + , platform(Windows), additionalLibraries(0), disabledLibraries(0) + , updateFileFlags(0), json(0) {} + + bool plugins; + bool libraries; + bool quickImports; + bool translations; + Platform platform; + unsigned additionalLibraries; + unsigned disabledLibraries; + unsigned updateFileFlags; + QString directory; + QString libraryDirectory; + QString binary; + JsonOutput *json; +}; + +// Return binary from folder +static inline QString findBinary(const QString &directory, Platform platform) +{ + QDir dir(QDir::cleanPath(directory)); + + const QStringList nameFilters = (platform & WindowsBased) ? + QStringList(QStringLiteral("*.exe")) : QStringList(); + foreach (const QString &binary, dir.entryList(nameFilters, QDir::Files | QDir::Executable)) + if (!binary.contains(QLatin1String(webProcessC), Qt::CaseInsensitive)) + return dir.filePath(binary); + return QString(); +} + +enum CommandLineParseFlag { + CommandLineParseError = 0x1, + CommandLineParseHelpRequested = 0x2 +}; + +static inline int parseArguments(const QStringList &arguments, QCommandLineParser *parser, + Options *options, QString *errorMessage) +{ + typedef QSharedPointer<QCommandLineOption> CommandLineOptionPtr; + typedef QPair<CommandLineOptionPtr, unsigned> OptionMaskPair; + typedef QVector<OptionMaskPair> OptionMaskVector; + + parser->setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + parser->setApplicationDescription(QStringLiteral("Qt Deploy Tool ") + QLatin1String(QT_VERSION_STR)); + const QCommandLineOption helpOption = parser->addHelpOption(); + parser->addVersionOption(); + + QCommandLineOption dirOption(QStringLiteral("dir"), + QStringLiteral("Use directory instead of binary directory."), + QStringLiteral("directory")); + parser->addOption(dirOption); + + QCommandLineOption libDirOption(QStringLiteral("libdir"), + QStringLiteral("Copy libraries to path."), + QStringLiteral("path")); + parser->addOption(libDirOption); + + QCommandLineOption forceOption(QStringLiteral("force"), + QStringLiteral("Force updating files.")); + parser->addOption(forceOption); + + QCommandLineOption noPluginsOption(QStringLiteral("no-plugins"), + QStringLiteral("Skip plugin deployment.")); + parser->addOption(noPluginsOption); + + QCommandLineOption noLibraryOption(QStringLiteral("no-libraries"), + QStringLiteral("Skip library deployment.")); + parser->addOption(noLibraryOption); + + QCommandLineOption noQuickImportOption(QStringLiteral("no-quick-import"), + QStringLiteral("Skip deployment of Qt Quick imports.")); + parser->addOption(noQuickImportOption); + + QCommandLineOption noTranslationOption(QStringLiteral("no-translations"), + QStringLiteral("Skip deployment of translations.")); + parser->addOption(noTranslationOption); + + QCommandLineOption webKitOption(QStringLiteral("webkit2"), + QStringLiteral("Deployment of WebKit2 (web process).")); + parser->addOption(webKitOption); + + QCommandLineOption noWebKitOption(QStringLiteral("no-webkit2"), + QStringLiteral("Skip deployment of WebKit2.")); + parser->addOption(noWebKitOption); + + QCommandLineOption jsonOption(QStringLiteral("json"), + QStringLiteral("Print to stdout in JSON format.")); + parser->addOption(jsonOption); + + QCommandLineOption verboseOption(QStringLiteral("verbose"), + QStringLiteral("Verbose level."), + QStringLiteral("level")); + parser->addOption(verboseOption); + + parser->addPositionalArgument(QStringLiteral("[file]"), + QStringLiteral("Binary or directory containing the binary.")); + + OptionMaskVector enabledModules; + OptionMaskVector disabledModules; + const size_t qtModulesCount = sizeof(qtModuleEntries)/sizeof(QtModuleEntry); + for (size_t i = 0; i < qtModulesCount; ++i) { + const QString option = QLatin1String(qtModuleEntries[i].option); + const QString name = QLatin1String(qtModuleEntries[i].libraryName); + const QString enabledDescription = QStringLiteral("Add ") + name + QStringLiteral(" module."); + CommandLineOptionPtr enabledOption(new QCommandLineOption(option, enabledDescription)); + parser->addOption(*enabledOption.data()); + enabledModules.push_back(OptionMaskPair(enabledOption, qtModuleEntries[i].module)); + + const QString disabledDescription = QStringLiteral("Remove ") + name + QStringLiteral(" module."); + CommandLineOptionPtr disabledOption(new QCommandLineOption(QStringLiteral("no-") + option, + disabledDescription)); + disabledModules.push_back(OptionMaskPair(disabledOption, qtModuleEntries[i].module)); + parser->addOption(*disabledOption.data()); + } + + const bool success = parser->parse(arguments); + if (parser->isSet(helpOption)) + return CommandLineParseHelpRequested; + if (!success) { + *errorMessage = parser->errorText(); + return CommandLineParseError; + } + + options->libraryDirectory = parser->value(libDirOption); + options->plugins = !parser->isSet(noPluginsOption); + options->libraries = !parser->isSet(noLibraryOption); + options->translations = !parser->isSet(noTranslationOption); + options->quickImports = !parser->isSet(noQuickImportOption); + if (parser->isSet(forceOption)) + options->updateFileFlags |= ForceUpdateFile; + + for (size_t i = 0; i < qtModulesCount; ++i) { + if (parser->isSet(*enabledModules.at(int(i)).first.data())) + options->additionalLibraries |= enabledModules.at(int(i)).second; + if (parser->isSet(*disabledModules.at(int(i)).first.data())) + options->disabledLibraries |= disabledModules.at(int(i)).second; + } + + if (parser->isSet(jsonOption)) { + 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(); + if (posArgs.isEmpty()) { + *errorMessage = QStringLiteral("Please specify the binary or folder."); + return CommandLineParseError | CommandLineParseHelpRequested; + } + if (posArgs.size() > 1) { + QStringList superfluousArguments = posArgs; + superfluousArguments.pop_front(); + *errorMessage = QStringLiteral("Superfluous arguments specified: ") + superfluousArguments.join(QLatin1Char(',')); + return CommandLineParseError; + } + + if (parser->isSet(dirOption)) + options->directory = parser->value(dirOption); + + const QString &file = posArgs.front(); + const QFileInfo fi(QDir::cleanPath(file)); + if (!fi.exists()) { + *errorMessage = QLatin1Char('"') + file + QStringLiteral("\" does not exist."); + return CommandLineParseError; + } + + if (!options->directory.isEmpty() && !fi.isFile()) { // -dir was specified - expecting file. + *errorMessage = QLatin1Char('"') + file + QStringLiteral("\" is not an executable file."); + return CommandLineParseError; + } + + if (fi.isFile()) { + options->binary = fi.absoluteFilePath(); + if (options->directory.isEmpty()) + options->directory = fi.absolutePath(); + } else { + options->binary = findBinary(fi.absoluteFilePath(), options->platform); + if (options->binary.isEmpty()) { + *errorMessage = QStringLiteral("Unable to find binary in \"") + file + QLatin1Char('"'); + return CommandLineParseError; + } + options->directory = fi.absoluteFilePath(); + } // directory. + return 0; +} + +// Simple line wrapping at 80 character boundaries. +static inline QString lineBreak(QString s) +{ + for (int i = 80; i < s.size(); i += 80) { + const int lastBlank = s.lastIndexOf(QLatin1Char(' '), i); + if (lastBlank >= 0) { + s[lastBlank] = QLatin1Char('\n'); + i = lastBlank + 1; + } + } + return s; +} + +static inline QString helpText(const QCommandLineParser &p) +{ + QString result = p.helpText(); + // Replace the default-generated text which is too long by a short summary + // explaining how to enable single libraries. + const int moduleStart = result.indexOf(QLatin1String("\n --core")); + const int argumentsStart = result.lastIndexOf(QLatin1String("\nArguments:")); + if (moduleStart >= argumentsStart) + return result; + QString moduleHelp = QLatin1String( + "\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"); + moduleHelp += lineBreak(QString::fromLatin1(formatQtModules(0xFFFFFFFF, true))); + moduleHelp += QLatin1Char('\n'); + result.replace(moduleStart, argumentsStart - moduleStart, moduleHelp); + return result; +} + +// Helper for recursively finding all dependent Qt libraries. +static bool findDependentQtLibraries(const QString &qtBinDir, const QString &binary, Platform platform, + QString *errorMessage, QStringList *result, + unsigned *wordSize = 0, bool *isDebug = 0, + int *directDependencyCount = 0, int recursionDepth = 0) +{ + QStringList dependentLibs; + if (directDependencyCount) + *directDependencyCount = 0; + if (!readExecutable(binary, platform, errorMessage, &dependentLibs, wordSize, isDebug)) { + errorMessage->prepend(QLatin1String("Unable to find dependent libraries of ") + + QDir::toNativeSeparators(binary) + QLatin1String(" :")); + return false; + } + // Filter out the Qt libraries. Note that depends.exe finds libs from optDirectory if we + // are run the 2nd time (updating). We want to check against the Qt bin dir libraries + const int start = result->size(); + const QRegExp filterRegExp(QStringLiteral("Qt5"), Qt::CaseInsensitive, QRegExp::FixedString); + foreach (const QString &qtLib, dependentLibs.filter(filterRegExp)) { + const QString path = normalizeFileName(qtBinDir + QLatin1Char('/') + QFileInfo(qtLib).fileName()); + if (!result->contains(path)) + result->append(path); + } + const int end = result->size(); + if (directDependencyCount) + *directDependencyCount = end - start; + // Recurse + for (int i = start; i < end; ++i) + if (!findDependentQtLibraries(qtBinDir, result->at(i), platform, errorMessage, result, 0, 0, 0, recursionDepth + 1)) + return false; + return true; +} + +// Base class to filter debug/release Windows DLLs for functions to be passed to updateFile(). +// Tries to pre-filter by namefilter and does check via PE. +class DllDirectoryFileEntryFunction { +public: + explicit DllDirectoryFileEntryFunction(bool debug, const QString &prefix = QLatin1String("*")) : + m_nameFilter(QStringList(prefix + (debug ? QStringLiteral("d.dll") : QStringLiteral(".dll")))), + m_dllDebug(debug) {} + + QStringList operator()(const QDir &dir) const + { + QStringList result; + QString errorMessage; + foreach (const QString &dll, m_nameFilter(dir)) { + const QString dllPath = dir.absoluteFilePath(dll); + bool debugDll; + if (readPeExecutable(dllPath, &errorMessage, 0, 0, &debugDll)) { + if (debugDll == m_dllDebug) { + result.push_back(dll); + } + } else { + std::fprintf(stderr, "Warning: Unable to read %s: %s", + qPrintable(QDir::toNativeSeparators(dllPath)), qPrintable(errorMessage)); + } + } + return result; + } + +private: + const NameFilterFileEntryFunction m_nameFilter; + const bool m_dllDebug; +}; + +// File entry filter function for updateFile() that returns a list of files for +// QML import trees: DLLs (matching debgug) and .qml/,js, etc. +class QmlDirectoryFileEntryFunction { +public: + explicit QmlDirectoryFileEntryFunction(bool debug) + : m_qmlNameFilter(QStringList() << QStringLiteral("*.js") << QStringLiteral("qmldir") << QStringLiteral("*.qmltypes") << QStringLiteral("*.png")) + , m_dllFilter(debug) + {} + + QStringList operator()(const QDir &dir) const { return m_dllFilter(dir) + m_qmlNameFilter(dir); } + +private: + NameFilterFileEntryFunction m_qmlNameFilter; + DllDirectoryFileEntryFunction m_dllFilter; +}; + +static inline unsigned qtModuleForPlugin(const QString &subDirName) +{ + if (subDirName == QLatin1String("accessible") || subDirName == QLatin1String("iconengines") + || subDirName == QLatin1String("imageformats") || subDirName == QLatin1String("platforms")) { + return QtGuiModule; + } + if (subDirName == QLatin1String("bearer")) + return QtNetworkModule; + if (subDirName == QLatin1String("sqldrivers")) + return QtSqlModule; + if (subDirName == QLatin1String("mediaservice") || subDirName == QLatin1String("playlistformats")) + return QtMultimediaModule; + if (subDirName == QLatin1String("printsupport")) + return QtPrintSupportModule; + if (subDirName == QLatin1String("qmltooling")) + return QtDeclarativeModule | QtQuickModule; + return 0; // "designer" +} + +QStringList findQtPlugins(unsigned usedQtModules, + const QString qtPluginsDirName, + bool debug, Platform platform, + QString *platformPlugin) +{ + if (qtPluginsDirName.isEmpty()) + return QStringList(); + QDir pluginsDir(qtPluginsDirName); + QStringList result; + foreach (const QString &subDirName, pluginsDir.entryList(QStringList(QLatin1String("*")), QDir::Dirs | QDir::NoDotAndDotDot)) { + const unsigned module = qtModuleForPlugin(subDirName); + if (module & usedQtModules) { + const QString subDirPath = qtPluginsDirName + QLatin1Char('/') + subDirName; + QDir subDir(subDirPath); + // Filter for platform or any. + QString filter; + const bool isPlatformPlugin = subDirName == QLatin1String("platforms"); + if (isPlatformPlugin) { + switch (platform) { + case Windows: + filter = QStringLiteral("qwindows"); + break; + case WinRtIntel: + case WinRtArm: + case WinPhoneIntel: + case WinPhoneArm: + filter = QStringLiteral("qwinrt"); + break; + case Unix: + filter = QStringLiteral("libqxcb"); + break; + case UnknownPlatform: + break; + } + } else { + filter = QLatin1String("*"); + } + const QStringList plugins = platform == Unix ? + NameFilterFileEntryFunction(QStringList(filter + QStringLiteral(".so")))(subDir) : + DllDirectoryFileEntryFunction(debug, filter)(subDir); + foreach (const QString &plugin, plugins) { + const QString pluginPath = subDir.absoluteFilePath(plugin); + if (isPlatformPlugin) + *platformPlugin = pluginPath; + result.push_back(pluginPath); + } // for filter + } // type matches + } // for plugin folder + return result; +} + +static unsigned qtModule(const QString &module) +{ + const size_t qtModulesCount = sizeof(qtModuleEntries)/sizeof(QtModuleEntry); + for (size_t i = 0; i < qtModulesCount; ++i) + if (module.contains(QLatin1String(qtModuleEntries[i].libraryName), Qt::CaseInsensitive)) + return qtModuleEntries[i].module; + return 0; +} + +static QStringList translationNameFilters(unsigned modules, const QString &prefix) +{ + QStringList result; + const size_t qtModulesCount = sizeof(qtModuleEntries)/sizeof(QtModuleEntry); + for (size_t i = 0; i < qtModulesCount; ++i) { + if ((qtModuleEntries[i].module & modules) && qtModuleEntries[i].translation) { + const QString name = QLatin1String(qtModuleEntries[i].translation) + + QLatin1Char('_') + prefix + QStringLiteral(".qm"); + if (!result.contains(name)) + result.push_back(name); + } + } + return result; +} + +static bool deployTranslations(const QString &sourcePath, unsigned usedQtModules, + const QString &target, QString *errorMessage) +{ + // Find available languages prefixes by checking on qtbase. + QStringList prefixes; + QDir sourceDir(sourcePath); + const QStringList qmFilter = QStringList(QStringLiteral("qtbase_*.qm")); + foreach (QString qmFile, sourceDir.entryList(qmFilter)) { + qmFile.chop(3); + qmFile.remove(0, 7); + prefixes.push_back(qmFile); + } + if (prefixes.isEmpty()) { + fprintf(stderr, "Warning: Could not find any translations in %s (developer build?)\n.", + qPrintable(QDir::toNativeSeparators(sourcePath))); + return true; + } + // Run lconvert to concatenate all files into a single named "qt_<prefix>.qm" in the application folder + // Use QT_INSTALL_TRANSLATIONS as working directory to keep the command line short. + const QString absoluteTarget = QFileInfo(target).absoluteFilePath(); + const QString binary = QStringLiteral("lconvert"); + QStringList arguments; + foreach (const QString &prefix, prefixes) { + const QString targetFile = QStringLiteral("qt_") + prefix + QStringLiteral(".qm"); + arguments.append(QStringLiteral("-o")); + arguments.append(QDir::toNativeSeparators(absoluteTarget + QLatin1Char('/') + targetFile)); + foreach (const QString &qmFile, sourceDir.entryList(translationNameFilters(usedQtModules, prefix))) + arguments.append(qmFile); + if (optVerboseLevel) + std::printf("Creating %s...\n", qPrintable(targetFile)); + unsigned long exitCode; + if (!runProcess(binary, arguments, sourcePath, &exitCode, 0, 0, errorMessage) || exitCode) + return false; + } // for prefixes. + return true; +} + +struct DeployResult +{ + DeployResult() : success(false), directlyUsedQtLibraries(0), usedQtLibraries(0), deployedQtLibraries(0) {} + operator bool() const { return success; } + + bool success; + unsigned directlyUsedQtLibraries; + unsigned usedQtLibraries; + unsigned deployedQtLibraries; +}; + +static QString libraryPath(const QString &libraryLocation, const char *name, Platform platform, bool debug) +{ + QString result = libraryLocation + QLatin1Char('/'); + if (platform & WindowsBased) { + result += QLatin1String(name); + if (debug) + result += QLatin1Char('d'); + result += QStringLiteral(".dll"); + } else if (platform & UnixBased) { + result += QStringLiteral("lib"); + result += QLatin1String(name); + result += QStringLiteral(".so"); + } + return result; +} + +static DeployResult deploy(const Options &options, + const QMap<QString, QString> &qmakeVariables, + QString *errorMessage) +{ + DeployResult result; + + const QChar slash = QLatin1Char('/'); + + const QString qtBinDir = qmakeVariables.value(QStringLiteral("QT_INSTALL_BINS")); + const QString libraryLocation = options.platform == Unix ? qmakeVariables.value(QStringLiteral("QT_INSTALL_LIBS")) : qtBinDir; + + if (optVerboseLevel > 1) + std::printf("Qt binaries in %s\n", qPrintable(QDir::toNativeSeparators(qtBinDir))); + + QStringList dependentQtLibs; + bool isDebug; + unsigned wordSize; + int directDependencyCount; + if (!findDependentQtLibraries(libraryLocation, options.binary, options.platform, errorMessage, &dependentQtLibs, &wordSize, &isDebug, &directDependencyCount)) + return result; + + if (optVerboseLevel) { + std::printf("%s: %ubit, %s executable.\n", qPrintable(QDir::toNativeSeparators(options.binary)), + wordSize, isDebug ? "debug" : "release"); + } + + if (dependentQtLibs.isEmpty()) { + *errorMessage = QDir::toNativeSeparators(options.binary) + QStringLiteral(" does not seem to be a Qt executable."); + return result; + } + + // Some Windows-specific checks in QtCore: ICU + if (options.platform & WindowsBased) { + const QStringList qt5Core = dependentQtLibs.filter(QStringLiteral("Qt5Core"), Qt::CaseInsensitive); + if (!qt5Core.isEmpty()) { + QStringList icuLibs = findDependentLibraries(qt5Core.front(), options.platform, 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. + QRegExp numberExpression(QStringLiteral("\\d+")); + Q_ASSERT(numberExpression.isValid()); + const int index = numberExpression.indexIn(icuLibs.front()); + if (index >= 0) { + const QString icuVersion = icuLibs.front().mid(index, numberExpression.matchedLength()); + if (optVerboseLevel > 1) + std::printf("Adding ICU version %s\n", qPrintable(icuVersion)); + icuLibs.push_back(QStringLiteral("icudt") + icuVersion + QStringLiteral(".dll")); + } + foreach (const QString &icuLib, icuLibs) { + const QString icuPath = findInPath(icuLib); + if (icuPath.isEmpty()) { + *errorMessage = QStringLiteral("Unable to locate ICU library ") + icuLib; + return result; + } + dependentQtLibs.push_back(icuPath); + } // foreach icuLib + } // !icuLibs.isEmpty() + } // Qt5Core + } // Windows + + // Find the plugins and check whether ANGLE, D3D are required on the platform plugin. + QString platformPlugin; + // Sort apart Qt 5 libraries in the ones that are represented by the + // QtModule enumeration (and thus controlled by flags) and others. + QStringList deployedQtLibraries; + for (int i = 0 ; i < dependentQtLibs.size(); ++i) { + if (const unsigned qtm = qtModule(dependentQtLibs.at(i))) { + result.usedQtLibraries |= qtm; + if (i < directDependencyCount) + result.directlyUsedQtLibraries |= qtm; + } else { + deployedQtLibraries.push_back(dependentQtLibs.at(i)); // Not represented by flag. + } + } + result.deployedQtLibraries = (result.usedQtLibraries | options.additionalLibraries) & ~options.disabledLibraries; + // Apply options flags and re-add library names. + const size_t qtModulesCount = sizeof(qtModuleEntries)/sizeof(QtModuleEntry); + for (size_t i = 0; i < qtModulesCount; ++i) + if (result.deployedQtLibraries & qtModuleEntries[i].module) + deployedQtLibraries.push_back(libraryPath(libraryLocation, qtModuleEntries[i].libraryName, options.platform, isDebug)); + + if (optVerboseLevel >= 1) { + std::printf("Direct dependencies: %s\nAll dependencies : %s\nTo be deployed : %s\n", + formatQtModules(result.directlyUsedQtLibraries).constData(), + formatQtModules(result.usedQtLibraries).constData(), + formatQtModules(result.deployedQtLibraries).constData()); + } + + const QStringList plugins = findQtPlugins(result.deployedQtLibraries, qmakeVariables.value(QStringLiteral("QT_INSTALL_PLUGINS")), + isDebug, options.platform, &platformPlugin); + if (optVerboseLevel > 1) + std::printf("Plugins: %s\n", qPrintable(plugins.join(QLatin1Char(',')))); + + if (plugins.isEmpty()) + return result; + + if (platformPlugin.isEmpty()) { + *errorMessage =QStringLiteral("Unable to find the platform plugin."); + return result; + } + + // Check for ANGLE on the platform plugin. + if (options.platform & WindowsBased) { + const QStringList platformPluginLibraries = findDependentLibraries(platformPlugin, options.platform, errorMessage); + const QStringList libEgl = platformPluginLibraries.filter(QStringLiteral("libegl"), Qt::CaseInsensitive); + if (!libEgl.isEmpty()) { + const QString libEglFullPath = qtBinDir + slash + QFileInfo(libEgl.front()).fileName(); + deployedQtLibraries.push_back(libEglFullPath); + const QStringList libGLESv2 = findDependentLibraries(libEglFullPath, options.platform, errorMessage).filter(QStringLiteral("libGLESv2"), Qt::CaseInsensitive); + if (!libGLESv2.isEmpty()) { + const QString libGLESv2FullPath = qtBinDir + slash + QFileInfo(libGLESv2.front()).fileName(); + deployedQtLibraries.push_back(libGLESv2FullPath); + } + // Find the D3d Compiler matching the D3D library. + const QString d3dCompiler = findD3dCompiler(options.platform, wordSize); + if (d3dCompiler.isEmpty()) { + std::fprintf(stderr, "Warning: Cannot find any version of the d3dcompiler DLL.\n"); + } else { + deployedQtLibraries.push_back(d3dCompiler); + } + } // !libEgl.isEmpty() + } // Windows + + // Update libraries + if (options.libraries) { + const QString targetPath = options.libraryDirectory.isEmpty() ? + options.directory : options.libraryDirectory; + foreach (const QString &qtLib, deployedQtLibraries) { + if (!updateFile(qtLib, targetPath, options.updateFileFlags, errorMessage)) + return result; + if (options.json) + options.json->addFile(qtLib, targetPath); + } + } // optLibraries + + // Update plugins + if (options.plugins) { + QDir dir(options.directory); + foreach (const QString &plugin, plugins) { + const QString targetDirName = plugin.section(slash, -2, -2); + if (!dir.exists(targetDirName)) { + if (optVerboseLevel) + std::printf("Creating directory %s.\n", qPrintable(targetDirName)); + if (!dir.mkdir(targetDirName)) { + std::fprintf(stderr, "Cannot create %s.\n", qPrintable(targetDirName)); + *errorMessage = QStringLiteral("Cannot create ") + targetDirName + QLatin1Char('.'); + return result; + } + } + const QString targetPath = options.directory + slash + targetDirName; + if (!updateFile(plugin, targetPath, options.updateFileFlags, errorMessage)) + return result; + if (options.json) + options.json->addFile(plugin, targetPath); + } + } // optPlugins + + // Update Quick imports + const bool usesQuick1 = result.deployedQtLibraries & QtDeclarativeModule; + // Do not be fooled by QtWebKit.dll depending on Quick into always installing Quick imports + // for WebKit1-applications. Check direct dependency only. + const bool usesQuick2 = (result.directlyUsedQtLibraries & QtQuickModule) + || (options.additionalLibraries & QtQuickModule); + if (options.quickImports && (usesQuick1 || usesQuick2)) { + const QmlDirectoryFileEntryFunction qmlFileEntryFunction(isDebug); + if (usesQuick2) { + const QString quick2ImportPath = qmakeVariables.value(QStringLiteral("QT_INSTALL_QML")); + QStringList quick2Imports; + quick2Imports << QStringLiteral("QtQml") << QStringLiteral("QtQuick") << QStringLiteral("QtQuick.2"); + if (result.deployedQtLibraries & QtMultimediaModule) + quick2Imports << QStringLiteral("QtMultimedia"); + if (result.deployedQtLibraries & QtSensorsModule) + quick2Imports << QStringLiteral("QtSensors"); + if (result.deployedQtLibraries & QtWebKitModule) + quick2Imports << QStringLiteral("QtWebKit"); + foreach (const QString &quick2Import, quick2Imports) { + const QString sourceFile = quick2ImportPath + slash + quick2Import; + if (!updateFile(sourceFile, qmlFileEntryFunction, options.directory, options.updateFileFlags, errorMessage)) + return result; + if (options.json) + options.json->addFile(sourceFile, options.directory); + } + } // Quick 2 + if (usesQuick1) { + const QString quick1ImportPath = qmakeVariables.value(QStringLiteral("QT_INSTALL_IMPORTS")); + QStringList quick1Imports(QStringLiteral("Qt")); + if (result.deployedQtLibraries & QtWebKitModule) + quick1Imports << QStringLiteral("QtWebKit"); + foreach (const QString &quick1Import, quick1Imports) { + const QString sourceFile = quick1ImportPath + slash + quick1Import; + if (!updateFile(sourceFile, qmlFileEntryFunction, options.directory, options.updateFileFlags, errorMessage)) + return result; + if (options.json) + options.json->addFile(sourceFile, options.directory); + } + } // Quick 1 + } // optQuickImports + + if (options.translations + && !deployTranslations(qmakeVariables.value(QStringLiteral("QT_INSTALL_TRANSLATIONS")), + result.deployedQtLibraries, options.directory, errorMessage)) { + return result; + } + + result.success = true; + return result; +} + +static bool deployWebKit2(const QMap<QString, QString> &qmakeVariables, + const Options &sourceOptions, QString *errorMessage) +{ + // Copy the web process and its dependencies + const QString webProcess = webProcessBinary(sourceOptions.platform); + const QString webProcessSource = qmakeVariables.value(QStringLiteral("QT_INSTALL_LIBEXECS")) + + QLatin1Char('/') + webProcess; + if (!updateFile(webProcessSource, sourceOptions.directory, sourceOptions.updateFileFlags, errorMessage)) + return false; + if (sourceOptions.json) + sourceOptions.json->addFile(webProcessSource, sourceOptions.directory); + Options options(sourceOptions); + options.binary = options.directory + QLatin1Char('/') + webProcess; + options.quickImports = false; + options.translations = false; + return deploy(options, qmakeVariables, errorMessage); +} + +int main(int argc, char **argv) +{ + QCoreApplication a(argc, argv); + QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); + + Options options; + QString errorMessage; + const QMap<QString, QString> qmakeVariables = queryQMakeAll(&errorMessage); + const QString xSpec = qmakeVariables.value(QStringLiteral("QMAKE_XSPEC")); + options.platform = platformFromMkSpec(xSpec); + + { // Command line + QCommandLineParser parser; + QString errorMessage; + const int result = parseArguments(QCoreApplication::arguments(), &parser, &options, &errorMessage); + if (result & CommandLineParseError) + std::fprintf(stderr, "%s\n\n", qPrintable(errorMessage)); + if (result & CommandLineParseHelpRequested) + std::fputs(qPrintable(helpText(parser)), stdout); + if (result & CommandLineParseError) + return 1; + if (result & CommandLineParseHelpRequested) + return 0; + } + + if (qmakeVariables.isEmpty() || xSpec.isEmpty() || !qmakeVariables.contains(QStringLiteral("QT_INSTALL_BINS"))) { + std::fprintf(stderr, "Unable to query qmake: %s\n", qPrintable(errorMessage)); + return 1; + } + + if (options.platform == UnknownPlatform) { + std::fprintf(stderr, "Unsupported platform %s\n", qPrintable(xSpec)); + return 1; + } + + // Create directories + if (!createDirectory(options.directory, &errorMessage)) { + std::fprintf(stderr, "%s\n", qPrintable(errorMessage)); + return 1; + } + if (!options.libraryDirectory.isEmpty() && options.libraryDirectory != options.directory + && !createDirectory(options.libraryDirectory, &errorMessage)) { + std::fprintf(stderr, "%s\n", qPrintable(errorMessage)); + return 1; + } + + if (optWebKit2) + options.additionalLibraries |= QtWebKitModule; + + + const DeployResult result = deploy(options, qmakeVariables, &errorMessage); + if (!result) { + std::fprintf(stderr, "%s\n", qPrintable(errorMessage)); + return 1; + } + + if ((optWebKit2 != -1) + && (optWebKit2 == 1 + || ((result.deployedQtLibraries & QtWebKitModule) + && (result.directlyUsedQtLibraries & QtQuickModule)))) { + if (optVerboseLevel) + std::printf("Deploying: %s...\n", webProcessC); + if (!deployWebKit2(qmakeVariables, options, &errorMessage)) { + std::fprintf(stderr, "%s\n", qPrintable(errorMessage)); + return 1; + } + } + + if (options.json) { + std::fputs(options.json->toJson().constData(), stdout); + delete options.json; + options.json = 0; + } + + return 0; +} + +QT_END_NAMESPACE diff --git a/src/windeployqt/utils.cpp b/src/windeployqt/utils.cpp new file mode 100644 index 000000000..2b045ad45 --- /dev/null +++ b/src/windeployqt/utils.cpp @@ -0,0 +1,805 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "utils.h" +#include "elfreader.h" + +#include <QtCore/QString> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QTemporaryFile> +#include <QtCore/QScopedPointer> +#include <QtCore/QScopedArrayPointer> +#include <QtCore/QStandardPaths> +#if defined(Q_OS_WIN) +# include <QtCore/qt_windows.h> +# include <Shlwapi.h> +#else // Q_OS_WIN +# include <sys/wait.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <unistd.h> +# include <stdlib.h> +# include <string.h> +# include <errno.h> +# include <fcntl.h> +#endif // !Q_OS_WIN + +#include <cstdio> + +QT_BEGIN_NAMESPACE + +int optVerboseLevel = 1; + +// Create a symbolic link by changing to the source directory to make sure the +// link uses relative paths only (QFile::link() otherwise uses the absolute path). +bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage) +{ + const QString oldDirectory = QDir::currentPath(); + if (!QDir::setCurrent(source.absolutePath())) { + *errorMessage = QStringLiteral("Unable to change to directory %1.").arg(QDir::toNativeSeparators(source.absolutePath())); + return false; + } + QFile file(source.fileName()); + const bool success = file.link(target); + QDir::setCurrent(oldDirectory); + if (!success) { + *errorMessage = QString::fromLatin1("Failed to create symbolic link %1 -> %2: %3") + .arg(QDir::toNativeSeparators(source.absoluteFilePath()), + QDir::toNativeSeparators(target), file.errorString()); + return false; + } + return true; +} + +bool createDirectory(const QString &directory, QString *errorMessage) +{ + const QFileInfo fi(directory); + if (fi.isDir()) + return true; + if (fi.exists()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a directory."). + arg(QDir::toNativeSeparators(directory)); + return false; + } + if (optVerboseLevel) + std::printf("Creating %s...\n", qPrintable(QDir::toNativeSeparators(directory))); + QDir dir; + if (!dir.mkpath(directory)) { + *errorMessage = QString::fromLatin1("Could not create directory %1."). + arg(QDir::toNativeSeparators(directory)); + return false; + } + return true; +} + +#ifdef Q_OS_WIN +QString winErrorMessage(unsigned long error) +{ + QString rc = QString::fromLatin1("#%1: ").arg(error); + ushort *lpMsgBuf; + + const int len = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, 0, (LPTSTR)&lpMsgBuf, 0, NULL); + if (len) { + rc = QString::fromUtf16(lpMsgBuf, len); + LocalFree(lpMsgBuf); + } else { + rc += QString::fromLatin1("<unknown error>"); + } + return rc; +} + +// Case-Normalize file name via GetShortPathNameW()/GetLongPathNameW() +QString normalizeFileName(const QString &name) +{ + wchar_t shortBuffer[MAX_PATH]; + const QString nativeFileName = QDir::toNativeSeparators(name); + if (!GetShortPathNameW((wchar_t *)nativeFileName.utf16(), shortBuffer, MAX_PATH)) + return name; + wchar_t result[MAX_PATH]; + if (!GetLongPathNameW(shortBuffer, result, MAX_PATH)) + return name; + return QDir::fromNativeSeparators(QString::fromWCharArray(result)); +} + +// Find a tool binary in the Windows SDK 8 +QString findSdkTool(const QString &tool) +{ + QStringList paths = QString::fromLocal8Bit(qgetenv("PATH")).split(QLatin1Char(';')); + const QByteArray sdkDir = qgetenv("WindowsSdkDir"); + if (!sdkDir.isEmpty()) + paths.prepend(QDir::cleanPath(QString::fromLocal8Bit(sdkDir)) + QLatin1String("/Tools/x64")); + return QStandardPaths::findExecutable(tool, paths); +} + +// runProcess helper: Create a temporary file for stdout/stderr redirection. +static HANDLE createInheritableTemporaryFile() +{ + wchar_t path[MAX_PATH]; + if (!GetTempPath(MAX_PATH, path)) + return INVALID_HANDLE_VALUE; + wchar_t name[MAX_PATH]; + if (!GetTempFileName(path, L"temp", 0, name)) // Creates file. + return INVALID_HANDLE_VALUE; + SECURITY_ATTRIBUTES securityAttributes; + ZeroMemory(&securityAttributes, sizeof(securityAttributes)); + securityAttributes.nLength = sizeof(securityAttributes); + securityAttributes.bInheritHandle = TRUE; + return CreateFile(name, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, &securityAttributes, + TRUNCATE_EXISTING, FILE_ATTRIBUTE_TEMPORARY, NULL); +} + +// runProcess helper: Rewind and read out a temporary file for stdout/stderr. +static inline void readTemporaryProcessFile(HANDLE handle, QByteArray *result) +{ + if (SetFilePointer(handle, 0, 0, FILE_BEGIN) == 0xFFFFFFFF) + return; + char buf[1024]; + DWORD bytesRead; + while (ReadFile(handle, buf, sizeof(buf), &bytesRead, NULL) && bytesRead) + result->append(buf, bytesRead); + CloseHandle(handle); +} + +static inline void appendToCommandLine(const QString &argument, QString *commandLine) +{ + const bool needsQuote = argument.contains(QLatin1Char(' ')); + if (!commandLine->isEmpty()) + commandLine->append(QLatin1Char(' ')); + if (needsQuote) + commandLine->append(QLatin1Char('"')); + commandLine->append(argument); + if (needsQuote) + commandLine->append(QLatin1Char('"')); +} + +// runProcess: Run a command line process (replacement for QProcess which +// does not exist in the bootstrap library). +bool runProcess(const QString &binary, const QStringList &args, + const QString &workingDirectory, + unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr, + QString *errorMessage) +{ + if (exitCode) + *exitCode = 0; + + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + STARTUPINFO myInfo; + GetStartupInfo(&myInfo); + si.hStdInput = myInfo.hStdInput; + si.hStdOutput = myInfo.hStdOutput; + si.hStdError = myInfo.hStdError; + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + const QChar backSlash = QLatin1Char('\\'); + QString nativeWorkingDir = QDir::toNativeSeparators(workingDirectory.isEmpty() ? QDir::currentPath() : workingDirectory); + if (!nativeWorkingDir.endsWith(backSlash)) + nativeWorkingDir += backSlash; + + if (stdOut) { + si.hStdOutput = createInheritableTemporaryFile(); + if (si.hStdOutput == INVALID_HANDLE_VALUE) { + if (errorMessage) + *errorMessage = QStringLiteral("Error creating stdout temporary file"); + return false; + } + si.dwFlags |= STARTF_USESTDHANDLES; + } + + if (stdErr) { + si.hStdError = createInheritableTemporaryFile(); + if (si.hStdError == INVALID_HANDLE_VALUE) { + if (errorMessage) + *errorMessage = QStringLiteral("Error creating stderr temporary file"); + return false; + } + si.dwFlags |= STARTF_USESTDHANDLES; + } + + // Create a copy of the command line which CreateProcessW can modify. + QString commandLine; + appendToCommandLine(binary, &commandLine); + foreach (const QString &a, args) + appendToCommandLine(a, &commandLine); + if (optVerboseLevel > 1) + std::printf("Running: %s\n", qPrintable(commandLine)); + + QScopedArrayPointer<wchar_t> commandLineW(new wchar_t[commandLine.size() + 1]); + commandLine.toWCharArray(commandLineW.data()); + commandLineW[commandLine.size()] = 0; + if (!CreateProcessW(0, commandLineW.data(), 0, 0, /* InheritHandles */ TRUE, 0, 0, + (wchar_t *)nativeWorkingDir.utf16(), &si, &pi)) { + if (stdOut) + CloseHandle(si.hStdOutput); + if (stdErr) + CloseHandle(si.hStdError); + if (errorMessage) + *errorMessage = QStringLiteral("CreateProcessW failed: ") + winErrorMessage(GetLastError()); + return false; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hThread); + if (exitCode) + GetExitCodeProcess(pi.hProcess, exitCode); + CloseHandle(pi.hProcess); + + if (stdOut) + readTemporaryProcessFile(si.hStdOutput, stdOut); + if (stdErr) + readTemporaryProcessFile(si.hStdError, stdErr); + return true; +} + +#else // Q_OS_WIN + +static inline char *encodeFileName(const QString &f) +{ + const QByteArray encoded = QFile::encodeName(f); + char *result = new char[encoded.size() + 1]; + strcpy(result, encoded.constData()); + return result; +} + +static inline char *tempFilePattern() +{ + QString path = QDir::tempPath(); + if (!path.endsWith(QLatin1Char('/'))) + path += QLatin1Char('/'); + path += QStringLiteral("tmpXXXXXX"); + return encodeFileName(path); +} + +static inline QByteArray readOutRedirectFile(int fd) +{ + enum { bufSize = 256 }; + + QByteArray result; + if (!lseek(fd, 0, 0)) { + char buf[bufSize]; + while (true) { + const ssize_t rs = read(fd, buf, bufSize); + if (rs <= 0) + break; + result.append(buf, int(rs)); + } + } + close(fd); + return result; +} + +// runProcess: Run a command line process (replacement for QProcess which +// does not exist in the bootstrap library). +bool runProcess(const QString &binary, const QStringList &args, + const QString &workingDirectory, + unsigned long *exitCode, QByteArray *stdOut, QByteArray *stdErr, + QString *errorMessage) +{ + QScopedArrayPointer<char> stdOutFileName; + QScopedArrayPointer<char> stdErrFileName; + + int stdOutFile = 0; + if (stdOut) { + stdOutFileName.reset(tempFilePattern()); + stdOutFile = mkstemp(stdOutFileName.data()); + if (stdOutFile < 0) { + *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno)); + return false; + } + } + + int stdErrFile = 0; + if (stdErr) { + stdErrFileName.reset(tempFilePattern()); + stdErrFile = mkstemp(stdErrFileName.data()); + if (stdErrFile < 0) { + *errorMessage = QStringLiteral("mkstemp() failed: ") + QString::fromLocal8Bit(strerror(errno)); + return false; + } + } + + const pid_t pID = fork(); + + if (pID < 0) { + *errorMessage = QStringLiteral("Fork failed: ") + QString::fromLocal8Bit(strerror(errno)); + return false; + } + + if (!pID) { // Child + if (stdOut) { + dup2(stdOutFile, STDOUT_FILENO); + close(stdOutFile); + } + if (stdErr) { + dup2(stdErrFile, STDERR_FILENO); + close(stdErrFile); + } + + if (!workingDirectory.isEmpty() && !QDir::setCurrent(workingDirectory)) { + std::fprintf(stderr, "Failed to change working directory to %s.\n", qPrintable(workingDirectory)); + ::_exit(-1); + } + + char **argv = new char *[args.size() + 2]; // Create argv. + char **ap = argv; + *ap++ = encodeFileName(binary); + foreach (const QString &a, args) + *ap++ = encodeFileName(a); + *ap = 0; + + execvp(argv[0], argv); + ::_exit(-1); + } + + int status; + pid_t waitResult; + + do { + waitResult = waitpid(pID, &status, 0); + } while (waitResult == -1 && errno == EINTR); + + if (stdOut) { + *stdOut = readOutRedirectFile(stdOutFile); + unlink(stdOutFileName.data()); + } + if (stdErr) { + *stdErr = readOutRedirectFile(stdErrFile); + unlink(stdErrFileName.data()); + } + + if (waitResult < 0) { + *errorMessage = QStringLiteral("Wait failed: ") + QString::fromLocal8Bit(strerror(errno)); + return false; + } + if (!WIFEXITED(status)) { + *errorMessage = binary + QStringLiteral(" did not exit cleanly."); + return false; + } + if (exitCode) + *exitCode = WEXITSTATUS(status); + return true; +} + +#endif // !Q_OS_WIN + +// Find a file in the path using ShellAPI. This can be used to locate DLLs which +// QStandardPaths cannot do. +QString findInPath(const QString &file) +{ +#if defined(Q_OS_WIN) + if (file.size() < MAX_PATH - 1) { + wchar_t buffer[MAX_PATH]; + file.toWCharArray(buffer); + buffer[file.size()] = 0; + if (PathFindOnPath(buffer, NULL)) + return QDir::cleanPath(QString::fromWCharArray(buffer)); + } + return QString(); +#else // Q_OS_WIN + return QStandardPaths::findExecutable(file); +#endif // !Q_OS_WIN +} + +QMap<QString, QString> queryQMakeAll(QString *errorMessage) +{ + QByteArray stdOut; + QByteArray stdErr; + unsigned long exitCode = 0; + const QString binary = QStringLiteral("qmake"); + if (!runProcess(binary, QStringList(QStringLiteral("-query")), QString(), &exitCode, &stdOut, &stdErr, errorMessage)) + return QMap<QString, QString>(); + if (exitCode) { + *errorMessage = binary + QStringLiteral(" returns ") + QString::number(exitCode) + + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr); + return QMap<QString, QString>(); + } + const QString output = QString::fromLocal8Bit(stdOut).trimmed().remove(QLatin1Char('\r')); + QMap<QString, QString> result; + int pos = 0; + while (true) { + const int colonPos = output.indexOf(QLatin1Char(':'), pos); + if (colonPos < 0) + break; + const int endPos = output.indexOf(QLatin1Char('\n'), colonPos + 1); + if (endPos < 0) + break; + const QString key = output.mid(pos, colonPos - pos); + const QString value = output.mid(colonPos + 1, endPos - colonPos - 1); + result.insert(key, value); + pos = endPos + 1; + } + return result; +} + +QString queryQMake(const QString &variable, QString *errorMessage) +{ + QByteArray stdOut; + QByteArray stdErr; + unsigned long exitCode; + const QString binary = QStringLiteral("qmake"); + QStringList args; + args << QStringLiteral("-query ") << variable; + if (!runProcess(binary, args, QString(), &exitCode, &stdOut, &stdErr, errorMessage)) + return QString(); + if (exitCode) { + *errorMessage = binary + QStringLiteral(" returns ") + QString::number(exitCode) + + QStringLiteral(": ") + QString::fromLocal8Bit(stdErr); + return QString(); + } + return QString::fromLocal8Bit(stdOut).trimmed(); +} + +// Update a file or directory. +bool updateFile(const QString &sourceFileName, const QStringList &nameFilters, + const QString &targetDirectory, QString *errorMessage) +{ + const QFileInfo sourceFileInfo(sourceFileName); + const QString targetFileName = targetDirectory + QLatin1Char('/') + sourceFileInfo.fileName(); + if (optVerboseLevel > 1) + std::printf("Checking %s, %s\n", qPrintable(sourceFileName), qPrintable(targetFileName)); + + if (!sourceFileInfo.exists()) { + *errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + if (sourceFileInfo.isSymLink()) { + *errorMessage = QString::fromLatin1("Symbolic links are not supported (%1).") + .arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + const QFileInfo targetFileInfo(targetFileName); + + if (sourceFileInfo.isDir()) { + if (targetFileInfo.exists()) { + if (!targetFileInfo.isDir()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } // Not a directory. + } else { // exists. + QDir d(targetDirectory); + if (optVerboseLevel) + std::printf("Creating %s.\n", qPrintable( QDir::toNativeSeparators(targetFileName))); + if (!d.mkdir(sourceFileInfo.fileName())) { + *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.") + .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory)); + return false; + } + } + // Recurse into directory + QDir dir(sourceFileName); + const QStringList allEntries = dir.entryList(nameFilters, QDir::Files) + dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QString &entry, allEntries) + if (!updateFile(sourceFileName + QLatin1Char('/') + entry, nameFilters, targetFileName, errorMessage)) + return false; + return true; + } // Source is directory. + + if (targetFileInfo.exists()) { + if (targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) { + if (optVerboseLevel) + std::printf("%s is up to date.\n", qPrintable(sourceFileInfo.fileName())); + return true; + } + QFile targetFile(targetFileName); + if (!targetFile.remove()) { + *errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString()); + return false; + } + } // target exists + QFile file(sourceFileName); + if (optVerboseLevel) + std::printf("Updating %s.\n", qPrintable(sourceFileInfo.fileName())); + if (!file.copy(targetFileName)) { + *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3") + .arg(QDir::toNativeSeparators(sourceFileName), + QDir::toNativeSeparators(targetFileName), + file.errorString()); + return false; + } + 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; + } + foreach (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 + +// Helper for reading out PE executable files: Find a section header for an RVA +// (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32). +template <class ImageNtHeader> +const IMAGE_SECTION_HEADER *findSectionHeader(DWORD rva, const ImageNtHeader *nTHeader) +{ + const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader); + const IMAGE_SECTION_HEADER *sectionEnd = section + nTHeader->FileHeader.NumberOfSections; + for ( ; section < sectionEnd; ++section) + if (rva >= section->VirtualAddress && rva < (section->VirtualAddress + section->Misc.VirtualSize)) + return section; + return 0; +} + +// Helper for reading out PE executable files: convert RVA to pointer (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32). +template <class ImageNtHeader> +inline const void *rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader, const void *imageBase) +{ + const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader); + if (!sectionHdr) + return 0; + const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData; + return static_cast<const char *>(imageBase) + rva - delta; +} + +// Helper for reading out PE executable files: return word size of a IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32 +template <class ImageNtHeader> +inline unsigned ntHeaderWordSize(const ImageNtHeader *header) +{ + // defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC + enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b }; + if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic) + return 32; + if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic) + return 64; + return 0; +} + +// Helper for reading out PE executable files: Retrieve the NT image header of an +// executable via the legacy DOS header. +static IMAGE_NT_HEADERS *getNtHeader(void *fileMemory, QString *errorMessage) +{ + IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(fileMemory); + // Check DOS header consistency + if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER)) + || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { + *errorMessage = QString::fromLatin1("DOS header check failed."); + return 0; + } + // Retrieve NT header + char *ntHeaderC = static_cast<char *>(fileMemory) + dosHeader->e_lfanew; + IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC); + // check NT header consistency + if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature)) + || ntHeaders->Signature != IMAGE_NT_SIGNATURE + || IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) { + *errorMessage = QString::fromLatin1("NT header check failed."); + return 0; + } + // Check magic + if (!ntHeaderWordSize(ntHeaders)) { + *errorMessage = QString::fromLatin1("NT header check failed; magic %1 is invalid."). + arg(ntHeaders->OptionalHeader.Magic); + return 0; + } + // Check section headers + IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders); + if (IsBadReadPtr(sectionHeaders, ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) { + *errorMessage = QString::fromLatin1("NT header section header check failed."); + return 0; + } + return ntHeaders; +} + +// Helper for reading out PE executable files: Read out import sections from +// IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32. +template <class ImageNtHeader> +inline QStringList readImportSections(const ImageNtHeader *ntHeaders, const void *base, QString *errorMessage) +{ + // Get import directory entry RVA and read out + const DWORD importsStartRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + if (!importsStartRVA) { + *errorMessage = QString::fromLatin1("Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry."); + return QStringList(); + } + const IMAGE_IMPORT_DESCRIPTOR *importDesc = (const IMAGE_IMPORT_DESCRIPTOR *)rvaToPtr(importsStartRVA, ntHeaders, base); + if (!importDesc) { + *errorMessage = QString::fromLatin1("Failed to find IMAGE_IMPORT_DESCRIPTOR entry."); + return QStringList(); + } + QStringList result; + for ( ; importDesc->Name; ++importDesc) + result.push_back(QString::fromLocal8Bit((const char *)rvaToPtr(importDesc->Name, ntHeaders, base))); + return result; +} + +// Read a PE executable and determine dependent libraries, word size +// and debug flags. Note that the debug flag cannot be relied on for MinGW. +bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage, + QStringList *dependentLibrariesIn, unsigned *wordSizeIn, + bool *isDebugIn) +{ + bool result = false; + HANDLE hFile = NULL; + HANDLE hFileMap = NULL; + void *fileMemory = 0; + + if (dependentLibrariesIn) + dependentLibrariesIn->clear(); + if (wordSizeIn) + *wordSizeIn = 0; + if (isDebugIn) + *isDebugIn = false; + + do { + // Create a memory mapping of the file + hFile = CreateFile(reinterpret_cast<const WCHAR*>(peExecutableFileName.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) { + *errorMessage = QString::fromLatin1("Cannot open '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError())); + break; + } + + hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (hFileMap == NULL) { + *errorMessage = QString::fromLatin1("Cannot create file mapping of '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError())); + break; + } + + fileMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0); + if (!fileMemory) { + *errorMessage = QString::fromLatin1("Cannot map '%1': %2").arg(peExecutableFileName, winErrorMessage(GetLastError())); + break; + } + + const IMAGE_NT_HEADERS *ntHeaders = getNtHeader(fileMemory, errorMessage); + if (!ntHeaders) + break; + + const unsigned wordSize = ntHeaderWordSize(ntHeaders); + if (wordSizeIn) + *wordSizeIn = wordSize; + bool debug = false; + if (wordSize == 32) { + const IMAGE_NT_HEADERS32 *ntHeaders32 = reinterpret_cast<const IMAGE_NT_HEADERS32 *>(ntHeaders); + debug = ntHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; + if (dependentLibrariesIn) + *dependentLibrariesIn = readImportSections(ntHeaders32, fileMemory, errorMessage); + } else { + const IMAGE_NT_HEADERS64 *ntHeaders64 = reinterpret_cast<const IMAGE_NT_HEADERS64 *>(ntHeaders); + debug = ntHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; + if (dependentLibrariesIn) + *dependentLibrariesIn = readImportSections(ntHeaders64, fileMemory, errorMessage); + } + + if (isDebugIn) + *isDebugIn = debug; + result = true; + if (optVerboseLevel > 1) + std::printf("%s: %s %u bit, debug: %d\n", __FUNCTION__, + qPrintable(peExecutableFileName), wordSize, debug); + } while (false); + + if (fileMemory) + UnmapViewOfFile(fileMemory); + + if (hFileMap != NULL) + CloseHandle(hFileMap); + + if (hFile != NULL && hFile != INVALID_HANDLE_VALUE) + CloseHandle(hFile); + + return result; +} + +QString findD3dCompiler(Platform platform, unsigned wordSize) +{ + const QString prefix = QStringLiteral("D3Dcompiler_"); + const QString suffix = QStringLiteral(".dll"); + // Get the DLL from Kit 8.0 onwards + const QString kitDir = QString::fromLocal8Bit(qgetenv("WindowsSdkDir")); + if (!kitDir.isEmpty()) { + QString redistDirPath = QDir::cleanPath(kitDir) + QStringLiteral("/Redist/D3D/"); + if (platform & ArmBased) { + redistDirPath += QStringLiteral("arm"); + } else { + redistDirPath += wordSize == 32 ? QStringLiteral("x86") : QStringLiteral("x64"); + } + QDir redistDir(redistDirPath); + if (redistDir.exists()) { + const QFileInfoList files = redistDir.entryInfoList(QStringList(prefix + QLatin1Char('*') + suffix), QDir::Files); + if (!files.isEmpty()) + return files.front().absoluteFilePath(); + } + } + // Find the latest D3D compiler DLL in path (Windows 8.1 has d3dcompiler_47). + if (platform & IntelBased) { + for (int i = 47 ; i >= 40 ; --i) { + const QString dll = findInPath(prefix + QString::number(i) + suffix); + if (!dll.isEmpty()) + return dll; + } + } + return QString(); +} + +#else // Q_OS_WIN + +bool readPeExecutable(const QString &, QString *errorMessage, + QStringList *, unsigned *, bool *) +{ + *errorMessage = QStringLiteral("Not implemented."); + return false; +} + +QString findD3dCompiler(Platform, unsigned) +{ + return QString(); +} + +#endif // !Q_OS_WIN + +QT_END_NAMESPACE diff --git a/src/windeployqt/utils.h b/src/windeployqt/utils.h new file mode 100644 index 000000000..8ebf7d6ea --- /dev/null +++ b/src/windeployqt/utils.h @@ -0,0 +1,252 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef UTILS_H +#define UTILS_H + +#include <QStringList> +#include <QMap> +#include <QtCore/QFile> +#include <QtCore/QDir> +#include <QtCore/QDateTime> + +#include <cstdio> + +QT_BEGIN_NAMESPACE + +enum PlatformFlag { + WindowsBased = 0x1000, + UnixBased = 0x2000, + IntelBased = 0x4000, + ArmBased = 0x8000 +}; + +enum Platform { + Windows = WindowsBased + IntelBased, + WinRtIntel = WindowsBased + IntelBased + 1, + WinRtArm = WindowsBased + ArmBased + 2, + WinPhoneIntel = WindowsBased + IntelBased + 3, + WinPhoneArm = WindowsBased + ArmBased + 4, + Unix = UnixBased, + UnknownPlatform +}; + +#ifdef Q_OS_WIN +QString normalizeFileName(const QString &name); +QString winErrorMessage(unsigned long error); +QString findSdkTool(const QString &tool); +#else // !Q_OS_WIN +inline QString normalizeFileName(const QString &name) { return name; } +#endif // !Q_OS_WIN + +bool createSymbolicLink(const QFileInfo &source, const QString &target, QString *errorMessage); +bool createDirectory(const QString &directory, QString *errorMessage); +QString findInPath(const QString &file); +QMap<QString, QString> queryQMakeAll(QString *errorMessage); +QString queryQMake(const QString &variable, QString *errorMessage); +QStringList findDependentLibs(const QString &binary, QString *errorMessage); + +bool updateFile(const QString &sourceFileName, const QStringList &nameFilters, + const QString &targetDirectory, QString *errorMessage); +bool runProcess(const QString &binary, const QStringList &args, + const QString &workingDirectory = QString(), + unsigned long *exitCode = 0, QByteArray *stdOut = 0, QByteArray *stdErr = 0, + QString *errorMessage = 0); + +bool readPeExecutable(const QString &peExecutableFileName, QString *errorMessage, + QStringList *dependentLibraries = 0, unsigned *wordSize = 0, + bool *isDebug = 0); +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) +{ + return platform == Unix ? + readElfExecutable(executableFileName, errorMessage, dependentLibraries, wordSize, isDebug) : + readPeExecutable(executableFileName, errorMessage, dependentLibraries, wordSize, isDebug); +} + +// Return dependent modules of a PE executable files. + +inline QStringList findDependentLibraries(const QString &peExecutableFileName, Platform platform, QString *errorMessage) +{ + QStringList result; + readExecutable(peExecutableFileName, platform, errorMessage, &result); + return result; +} + +QString findD3dCompiler(Platform platform, unsigned wordSize); + +extern int optVerboseLevel; + +// Recursively update a file or directory, matching DirectoryFileEntryFunction against the QDir +// to obtain the files. +enum UpdateFileFlag { + ForceUpdateFile = 0x1 +}; + +template <class DirectoryFileEntryFunction> +bool updateFile(const QString &sourceFileName, + DirectoryFileEntryFunction directoryFileEntryFunction, + const QString &targetDirectory, + unsigned flags, + QString *errorMessage) +{ + const QFileInfo sourceFileInfo(sourceFileName); + const QString targetFileName = targetDirectory + QLatin1Char('/') + sourceFileInfo.fileName(); + if (optVerboseLevel > 1) + std::printf("Checking %s, %s\n", qPrintable(sourceFileName), qPrintable(targetFileName)); + + if (!sourceFileInfo.exists()) { + *errorMessage = QString::fromLatin1("%1 does not exist.").arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + const QFileInfo targetFileInfo(targetFileName); + + if (sourceFileInfo.isSymLink()) { + const QString sourcePath = sourceFileInfo.symLinkTarget(); + const QString relativeSource = QDir(sourceFileInfo.absolutePath()).relativeFilePath(sourcePath); + if (relativeSource.contains(QLatin1Char('/'))) { + *errorMessage = QString::fromLatin1("Symbolic links across directories are not supported (%1).") + .arg(QDir::toNativeSeparators(sourceFileName)); + return false; + } + + // Update the linked-to file + if (!updateFile(sourcePath, directoryFileEntryFunction, targetDirectory, flags, errorMessage)) + return false; + + if (targetFileInfo.exists()) { + if (!targetFileInfo.isSymLink()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a symbolic link.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } // Not a symlink + const QString relativeTarget = QDir(targetFileInfo.absolutePath()).relativeFilePath(targetFileInfo.symLinkTarget()); + if (relativeSource == relativeTarget) // Exists and points to same entry: happy. + return true; + QFile existingTargetFile(targetFileName); + if (!existingTargetFile.remove()) { + *errorMessage = QString::fromLatin1("Cannot remove existing symbolic link %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), existingTargetFile.errorString()); + return false; + } + } // target symbolic link exists + return createSymbolicLink(QFileInfo(targetDirectory + QLatin1Char('/') + relativeSource), sourceFileInfo.fileName(), errorMessage); + } // Source is symbolic link + + if (sourceFileInfo.isDir()) { + if (targetFileInfo.exists()) { + if (!targetFileInfo.isDir()) { + *errorMessage = QString::fromLatin1("%1 already exists and is not a directory.") + .arg(QDir::toNativeSeparators(targetFileName)); + return false; + } // Not a directory. + } else { // exists. + QDir d(targetDirectory); + if (optVerboseLevel) + std::printf("Creating %s.\n", qPrintable(targetFileName)); + if (!d.mkdir(sourceFileInfo.fileName())) { + *errorMessage = QString::fromLatin1("Cannot create directory %1 under %2.") + .arg(sourceFileInfo.fileName(), QDir::toNativeSeparators(targetDirectory)); + return false; + } + } + // Recurse into directory + QDir dir(sourceFileName); + + const QStringList allEntries = directoryFileEntryFunction(dir) + dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QString &entry, allEntries) + if (!updateFile(sourceFileName + QLatin1Char('/') + entry, directoryFileEntryFunction, targetFileName, flags, errorMessage)) + return false; + return true; + } // Source is directory. + + if (targetFileInfo.exists()) { + if (!(flags & ForceUpdateFile) + && targetFileInfo.lastModified() >= sourceFileInfo.lastModified()) { + if (optVerboseLevel) + std::printf("%s is up to date.\n", qPrintable(sourceFileInfo.fileName())); + return true; + } + QFile targetFile(targetFileName); + if (!targetFile.remove()) { + *errorMessage = QString::fromLatin1("Cannot remove existing file %1: %2") + .arg(QDir::toNativeSeparators(targetFileName), targetFile.errorString()); + return false; + } + } // target exists + QFile file(sourceFileName); + if (optVerboseLevel) + std::printf("Updating %s.\n", qPrintable(sourceFileInfo.fileName())); + if (!file.copy(targetFileName)) { + *errorMessage = QString::fromLatin1("Cannot copy %1 to %2: %3") + .arg(QDir::toNativeSeparators(sourceFileName), + QDir::toNativeSeparators(targetFileName), + file.errorString()); + return false; + } + return true; +} + +// Base class to filter files by name filters functions to be passed to updateFile(). +class NameFilterFileEntryFunction { +public: + explicit NameFilterFileEntryFunction(const QStringList &nameFilters) : m_nameFilters(nameFilters) {} + QStringList operator()(const QDir &dir) const { return dir.entryList(m_nameFilters, QDir::Files); } + +private: + const QStringList m_nameFilters; +}; + +// Convenience for all files. +inline bool updateFile(const QString &sourceFileName, const QString &targetDirectory, unsigned flags, QString *errorMessage) +{ + return updateFile(sourceFileName, NameFilterFileEntryFunction(QStringList()), targetDirectory, flags, errorMessage); +} + +QT_END_NAMESPACE + +#endif // UTILS_H diff --git a/src/windeployqt/windeployqt.pro b/src/windeployqt/windeployqt.pro new file mode 100644 index 000000000..6bf96a34d --- /dev/null +++ b/src/windeployqt/windeployqt.pro @@ -0,0 +1,14 @@ +option(host_build) +QT = core-private +DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII + +SOURCES += main.cpp utils.cpp elfreader.cpp +HEADERS += utils.h elfreader.h + +CONFIG += force_bootstrap console + +INCLUDEPATH += $$QT_BUILD_TREE/src/corelib/global + +win32: LIBS += -lShlwapi + +load(qt_tool) |