summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorArttu Tarkiainen <arttu.tarkiainen@qt.io>2020-11-20 17:09:30 +0200
committerArttu Tarkiainen <arttu.tarkiainen@qt.io>2020-12-02 10:25:18 +0000
commit885a41d1667ac8ccf59a4de76cb8449074a466ac (patch)
tree7e0909c5b06754f458c6b6e7d3a77a4ffd994be7 /src
parentf053b9a627921b03529b4f797a97b582675fbe71 (diff)
Tools: refactor to move general purpose functionality to installer lib
This makes it possible to utilize parts of our existing tooling in the offline installer from online installer generation process. Task-number: QTIFW-2048 Change-Id: I7ee605be75541cc83a3b6909089bda45f0835bcf Reviewed-by: Katja Marttila <katja.marttila@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/libs/ifwtools/binarycreator.cpp830
-rw-r--r--src/libs/ifwtools/binarycreator.h123
-rw-r--r--src/libs/ifwtools/ifwtools.pri19
-rw-r--r--src/libs/ifwtools/ifwtools_global.h44
-rw-r--r--src/libs/ifwtools/rcc/qcorecmdlineargs_p.h59
-rw-r--r--src/libs/ifwtools/rcc/rcc.cpp1038
-rw-r--r--src/libs/ifwtools/rcc/rcc.h140
-rw-r--r--src/libs/ifwtools/rcc/rccmain.cpp239
-rw-r--r--src/libs/ifwtools/repositorygen.cpp966
-rw-r--r--src/libs/ifwtools/repositorygen.h88
-rw-r--r--src/libs/ifwtools/resources/copylibsintobundle.sh189
-rw-r--r--src/libs/ifwtools/resources/default_icon_mac.icnsbin0 -> 118992 bytes
-rw-r--r--src/libs/ifwtools/resources/ifwtools.qrc7
-rw-r--r--src/libs/installer/installer.pro1
14 files changed, 3743 insertions, 0 deletions
diff --git a/src/libs/ifwtools/binarycreator.cpp b/src/libs/ifwtools/binarycreator.cpp
new file mode 100644
index 000000000..b3119f5be
--- /dev/null
+++ b/src/libs/ifwtools/binarycreator.cpp
@@ -0,0 +1,830 @@
+/**************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "binarycreator.h"
+
+#include "qtpatch.h"
+#include "repositorygen.h"
+#include "binarycontent.h"
+#include "errors.h"
+#include "fileio.h"
+#include "init.h"
+#include "repository.h"
+#include "settings.h"
+#include "utils.h"
+
+#include <QDateTime>
+#include <QDirIterator>
+#include <QDomDocument>
+#include <QProcess>
+#include <QRegExp>
+#include <QSettings>
+#include <QTemporaryFile>
+#include <QTemporaryDir>
+
+#include <iostream>
+
+#ifdef Q_OS_MACOS
+#include <QtCore/QtEndian>
+#include <QtCore/QFile>
+#include <QtCore/QVersionNumber>
+
+#include <mach-o/fat.h>
+#include <mach-o/loader.h>
+#endif
+
+using namespace QInstaller;
+using namespace QInstallerTools;
+
+#ifndef Q_OS_WIN
+static void chmod755(const QString &absolutFilePath)
+{
+ QFile::setPermissions(absolutFilePath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner
+ | QFile::ReadGroup | QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther);
+}
+#endif
+
+#ifdef Q_OS_MACOS
+template <typename T = uint32_t> T readInt(QIODevice *ioDevice, bool *ok,
+ bool swap, bool peek = false) {
+ const auto bytes = peek
+ ? ioDevice->peek(sizeof(T))
+ : ioDevice->read(sizeof(T));
+ if (bytes.size() != sizeof(T)) {
+ if (ok)
+ *ok = false;
+ return T();
+ }
+ if (ok)
+ *ok = true;
+ T n = *reinterpret_cast<const T *>(bytes.constData());
+ return swap ? qbswap(n) : n;
+}
+
+static QVersionNumber readMachOMinimumSystemVersion(QIODevice *device)
+{
+ bool ok;
+ std::vector<qint64> machoOffsets;
+
+ qint64 pos = device->pos();
+ uint32_t magic = readInt(device, &ok, false);
+ if (magic == FAT_MAGIC || magic == FAT_CIGAM ||
+ magic == FAT_MAGIC_64 || magic == FAT_CIGAM_64) {
+ bool swap = magic == FAT_CIGAM || magic == FAT_CIGAM_64;
+ uint32_t nfat = readInt(device, &ok, swap);
+ if (!ok)
+ return QVersionNumber();
+
+ for (uint32_t n = 0; n < nfat; ++n) {
+ const bool is64bit = magic == FAT_MAGIC_64 || magic == FAT_CIGAM_64;
+ fat_arch_64 fat_arch;
+ fat_arch.cputype = static_cast<cpu_type_t>(readInt(device, &ok, swap));
+ if (!ok)
+ return QVersionNumber();
+
+ fat_arch.cpusubtype = static_cast<cpu_subtype_t>(readInt(device, &ok, swap));
+ if (!ok)
+ return QVersionNumber();
+
+ fat_arch.offset = is64bit
+ ? readInt<uint64_t>(device, &ok, swap) : readInt(device, &ok, swap);
+ if (!ok)
+ return QVersionNumber();
+
+ fat_arch.size = is64bit
+ ? readInt<uint64_t>(device, &ok, swap) : readInt(device, &ok, swap);
+ if (!ok)
+ return QVersionNumber();
+
+ fat_arch.align = readInt(device, &ok, swap);
+ if (!ok)
+ return QVersionNumber();
+
+ fat_arch.reserved = is64bit ? readInt(device, &ok, swap) : 0;
+ if (!ok)
+ return QVersionNumber();
+
+ machoOffsets.push_back(static_cast<qint64>(fat_arch.offset));
+ }
+ } else if (!ok) {
+ return QVersionNumber();
+ }
+
+ // Wasn't a fat file, so we just read a thin Mach-O from the original offset
+ if (machoOffsets.empty())
+ machoOffsets.push_back(pos);
+
+ std::vector<QVersionNumber> versions;
+
+ for (const auto &offset : machoOffsets) {
+ if (!device->seek(offset))
+ return QVersionNumber();
+
+ bool swap = false;
+ mach_header_64 header;
+ header.magic = readInt(device, nullptr, swap);
+ switch (header.magic) {
+ case MH_CIGAM:
+ case MH_CIGAM_64:
+ swap = true;
+ break;
+ case MH_MAGIC:
+ case MH_MAGIC_64:
+ break;
+ default:
+ return QVersionNumber();
+ }
+
+ header.cputype = static_cast<cpu_type_t>(readInt(device, &ok, swap));
+ if (!ok)
+ return QVersionNumber();
+
+ header.cpusubtype = static_cast<cpu_subtype_t>(readInt(device, &ok, swap));
+ if (!ok)
+ return QVersionNumber();
+
+ header.filetype = readInt(device, &ok, swap);
+ if (!ok)
+ return QVersionNumber();
+
+ header.ncmds = readInt(device, &ok, swap);
+ if (!ok)
+ return QVersionNumber();
+
+ header.sizeofcmds = readInt(device, &ok, swap);
+ if (!ok)
+ return QVersionNumber();
+
+ header.flags = readInt(device, &ok, swap);
+ if (!ok)
+ return QVersionNumber();
+
+ header.reserved = header.magic == MH_MAGIC_64 || header.magic == MH_CIGAM_64
+ ? readInt(device, &ok, swap) : 0;
+ if (!ok)
+ return QVersionNumber();
+
+ for (uint32_t i = 0; i < header.ncmds; ++i) {
+ const uint32_t cmd = readInt(device, nullptr, swap);
+ const uint32_t cmdsize = readInt(device, nullptr, swap);
+ if (cmd == 0 || cmdsize == 0)
+ return QVersionNumber();
+
+ switch (cmd) {
+ case LC_VERSION_MIN_MACOSX:
+ case LC_VERSION_MIN_IPHONEOS:
+ case LC_VERSION_MIN_TVOS:
+ case LC_VERSION_MIN_WATCHOS:
+ const uint32_t version = readInt(device, &ok, swap, true);
+ if (!ok)
+ return QVersionNumber();
+
+ versions.push_back(QVersionNumber(
+ static_cast<int>(version >> 16),
+ static_cast<int>((version >> 8) & 0xff),
+ static_cast<int>(version & 0xff)));
+ break;
+ }
+
+ const qint64 remaining = static_cast<qint64>(cmdsize - sizeof(cmd) - sizeof(cmdsize));
+ if (device->read(remaining).size() != remaining)
+ return QVersionNumber();
+ }
+ }
+
+ std::sort(versions.begin(), versions.end());
+ return !versions.empty() ? versions.front() : QVersionNumber();
+}
+#endif
+
+static int assemble(Input input, const QInstaller::Settings &settings, const QString &signingIdentity)
+{
+#ifdef Q_OS_MACOS
+ if (QInstaller::isInBundle(input.installerExePath)) {
+ const QString bundle = input.installerExePath;
+ // if the input file was a bundle
+ const QSettings s(QString::fromLatin1("%1/Contents/Info.plist").arg(input.installerExePath),
+ QSettings::NativeFormat);
+ input.installerExePath = QString::fromLatin1("%1/Contents/MacOS/%2").arg(bundle)
+ .arg(s.value(QLatin1String("CFBundleExecutable"),
+ QFileInfo(input.installerExePath).completeBaseName()).toString());
+ }
+
+ const bool createDMG = input.outputPath.endsWith(QLatin1String(".dmg"));
+ if (createDMG)
+ input.outputPath.replace(input.outputPath.length() - 4, 4, QLatin1String(".app"));
+
+ const bool isBundle = input.outputPath.endsWith(QLatin1String(".app"));
+ const QString bundle = isBundle ? input.outputPath : QString();
+ const BundleBackup bundleBackup(bundle);
+
+ if (isBundle) {
+ // output should be a bundle
+ const QFileInfo fi(input.outputPath);
+
+ QString minimumSystemVersion;
+ QFile file(input.installerExePath);
+ if (file.open(QIODevice::ReadOnly))
+ minimumSystemVersion = readMachOMinimumSystemVersion(&file).normalized().toString();
+
+ const QString contentsResourcesPath = fi.filePath() + QLatin1String("/Contents/Resources/");
+
+ QInstaller::mkpath(fi.filePath() + QLatin1String("/Contents/MacOS"));
+ QInstaller::mkpath(contentsResourcesPath);
+
+ {
+ QFile pkgInfo(fi.filePath() + QLatin1String("/Contents/PkgInfo"));
+ pkgInfo.open(QIODevice::WriteOnly);
+ QTextStream pkgInfoStream(&pkgInfo);
+ pkgInfoStream << QLatin1String("APPL????") << endl;
+ }
+
+ QString iconFile;
+ if (QFile::exists(settings.installerApplicationIcon())) {
+ iconFile = settings.installerApplicationIcon();
+ } else {
+ iconFile = QString::fromLatin1(":/resources/default_icon_mac.icns");
+ }
+
+ const QString iconTargetFile = fi.completeBaseName() + QLatin1String(".icns");
+ QFile::copy(iconFile, contentsResourcesPath + iconTargetFile);
+ if (QDir(qApp->applicationDirPath() + QLatin1String("/qt_menu.nib")).exists()) {
+ copyDirectoryContents(qApp->applicationDirPath() + QLatin1String("/qt_menu.nib"),
+ contentsResourcesPath + QLatin1String("/qt_menu.nib"));
+ }
+
+ QFile infoPList(fi.filePath() + QLatin1String("/Contents/Info.plist"));
+ infoPList.open(QIODevice::WriteOnly);
+ QTextStream plistStream(&infoPList);
+ plistStream << QLatin1String("<?xml version=\"1.0\" encoding=\"UTF-8\"?>") << endl;
+ plistStream << QLatin1String("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
+ "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">") << endl;
+ plistStream << QLatin1String("<plist version=\"1.0\">") << endl;
+ plistStream << QLatin1String("<dict>") << endl;
+ plistStream << QLatin1String("\t<key>CFBundleIconFile</key>") << endl;
+ plistStream << QLatin1String("\t<string>") << iconTargetFile << QLatin1String("</string>")
+ << endl;
+ plistStream << QLatin1String("\t<key>CFBundlePackageType</key>") << endl;
+ plistStream << QLatin1String("\t<string>APPL</string>") << endl;
+#define QUOTE_(x) #x
+#define QUOTE(x) QUOTE_(x)
+ plistStream << QLatin1String("\t<key>CFBundleShortVersionString</key>") << endl;
+ plistStream << QLatin1String("\t<string>") << QLatin1String(QUOTE(IFW_VERSION_STR)) << ("</string>")
+ << endl;
+ plistStream << QLatin1String("\t<key>CFBundleVersion</key>") << endl;
+ plistStream << QLatin1String("\t<string>") << QLatin1String(QUOTE(IFW_VERSION_STR)) << ("</string>")
+ << endl;
+#undef QUOTE
+#undef QUOTE_
+ plistStream << QLatin1String("\t<key>CFBundleSignature</key>") << endl;
+ plistStream << QLatin1String("\t<string>\?\?\?\?</string>") << endl;
+ plistStream << QLatin1String("\t<key>CFBundleExecutable</key>") << endl;
+ plistStream << QLatin1String("\t<string>") << fi.completeBaseName() << QLatin1String("</string>")
+ << endl;
+ plistStream << QLatin1String("\t<key>CFBundleIdentifier</key>") << endl;
+ plistStream << QLatin1String("\t<string>com.yourcompany.installerbase</string>") << endl;
+ plistStream << QLatin1String("\t<key>NOTE</key>") << endl;
+ plistStream << QLatin1String("\t<string>This file was generated by Qt Installer Framework.</string>")
+ << endl;
+ plistStream << QLatin1String("\t<key>NSPrincipalClass</key>") << endl;
+ plistStream << QLatin1String("\t<string>NSApplication</string>") << endl;
+ if (!minimumSystemVersion.isEmpty()) {
+ plistStream << QLatin1String("\t<key>LSMinimumSystemVersion</key>") << endl;
+ plistStream << QLatin1String("\t<string>") << minimumSystemVersion << QLatin1String("</string>") << endl;
+ }
+ plistStream << QLatin1String("</dict>") << endl;
+ plistStream << QLatin1String("</plist>") << endl;
+
+ input.outputPath = QString::fromLatin1("%1/Contents/MacOS/%2").arg(input.outputPath)
+ .arg(fi.completeBaseName());
+ }
+#elif defined(Q_OS_LINUX)
+ Q_UNUSED(settings)
+#endif
+
+ QTemporaryFile file(input.outputPath);
+ if (!file.open()) {
+ throw Error(QString::fromLatin1("Cannot copy %1 to %2: %3").arg(input.installerExePath,
+ input.outputPath, file.errorString()));
+ }
+
+ const QString tempFile = file.fileName();
+ file.close();
+ file.remove();
+
+ QFile instExe(input.installerExePath);
+ if (!instExe.copy(tempFile)) {
+ throw Error(QString::fromLatin1("Cannot copy %1 to %2: %3").arg(instExe.fileName(),
+ tempFile, instExe.errorString()));
+ }
+
+ QtPatch::patchBinaryFile(tempFile, QByteArray("MY_InstallerCreateDateTime_MY"),
+ QDateTime::currentDateTime().toString(QLatin1String("yyyy-MM-dd - HH:mm:ss")).toLatin1());
+
+
+ input.installerExePath = tempFile;
+
+#if defined(Q_OS_WIN)
+ // setting the windows icon must happen before we append our binary data - otherwise they get lost :-/
+ if (QFile::exists(settings.installerApplicationIcon())) {
+ // no error handling as this is not fatal
+ setApplicationIcon(tempFile, settings.installerApplicationIcon());
+ }
+#elif defined(Q_OS_MACOS)
+ if (isBundle) {
+ // no error handling as this is not fatal
+ const QString copyscript = QDir::temp().absoluteFilePath(QLatin1String("copylibsintobundle.sh"));
+ QFile::copy(QLatin1String(":/resources/copylibsintobundle.sh"), copyscript);
+ QFile::rename(tempFile, input.outputPath);
+ chmod755(copyscript);
+ QProcess p;
+ p.start(copyscript, QStringList() << bundle);
+ p.waitForFinished(-1);
+ QFile::rename(input.outputPath, tempFile);
+ QFile::remove(copyscript);
+ }
+#endif
+
+ QFile out(generateTemporaryFileName());
+
+ QString targetName = input.outputPath;
+#ifdef Q_OS_MACOS
+ QDir resourcePath(QFileInfo(input.outputPath).dir());
+ resourcePath.cdUp();
+ resourcePath.cd(QLatin1String("Resources"));
+ targetName = resourcePath.filePath(QLatin1String("installer.dat"));
+#endif
+
+ {
+ QFile target(targetName);
+ if (target.exists() && !target.remove()) {
+ qCritical("Cannot remove target %s: %s", qPrintable(target.fileName()),
+ qPrintable(target.errorString()));
+ QFile::remove(tempFile);
+ return EXIT_FAILURE;
+ }
+ }
+
+ try {
+ QInstaller::openForWrite(&out);
+ QFile exe(input.installerExePath);
+
+#ifdef Q_OS_MACOS
+ if (!exe.copy(input.outputPath)) {
+ throw Error(QString::fromLatin1("Cannot copy %1 to %2: %3").arg(exe.fileName(),
+ input.outputPath, exe.errorString()));
+ }
+#else
+ QInstaller::openForRead(&exe);
+ QInstaller::appendData(&out, &exe, exe.size());
+#endif
+
+ foreach (const QInstallerTools::PackageInfo &info, input.packages) {
+ QInstaller::ResourceCollection collection;
+ collection.setName(info.name.toUtf8());
+
+ qDebug() << "Creating resource archive for" << info.name;
+ foreach (const QString &file, info.copiedFiles) {
+ const QSharedPointer<Resource> resource(new Resource(file));
+ qDebug().nospace() << "Appending " << file << " (" << humanReadableSize(resource->size()) << ")";
+ collection.appendResource(resource);
+ }
+ input.manager.insertCollection(collection);
+ }
+
+ const QList<QInstaller::OperationBlob> operations;
+ BinaryContent::writeBinaryContent(&out, operations, input.manager,
+ BinaryContent::MagicInstallerMarker, BinaryContent::MagicCookie);
+ } catch (const Error &e) {
+ qCritical("Error occurred while assembling the installer: %s", qPrintable(e.message()));
+ QFile::remove(tempFile);
+ return EXIT_FAILURE;
+ }
+
+ if (!out.rename(targetName)) {
+ qCritical("Cannot write installer to %s: %s", targetName.toUtf8().constData(),
+ out.errorString().toUtf8().constData());
+ QFile::remove(tempFile);
+ return EXIT_FAILURE;
+ }
+
+#ifndef Q_OS_WIN
+ chmod755(out.fileName());
+#endif
+ QFile::remove(tempFile);
+
+#ifdef Q_OS_MACOS
+ if (isBundle && !signingIdentity.isEmpty()) {
+ qDebug() << "Signing .app bundle...";
+
+ QProcess p;
+ p.start(QLatin1String("codesign"),
+ QStringList() << QLatin1String("--force")
+ << QLatin1String("--deep")
+ << QLatin1String("--sign") << signingIdentity
+ << bundle);
+
+ if (!p.waitForFinished(-1)) {
+ qCritical("Failed to sign app bundle: error while running '%s %s': %s",
+ p.program().toUtf8().constData(),
+ p.arguments().join(QLatin1Char(' ')).toUtf8().constData(),
+ p.errorString().toUtf8().constData());
+ return EXIT_FAILURE;
+ }
+
+ if (p.exitStatus() == QProcess::NormalExit) {
+ if (p.exitCode() != 0) {
+ qCritical("Failed to sign app bundle: running codesign failed "
+ "with exit code %d: %s", p.exitCode(),
+ p.readAllStandardError().constData());
+ return EXIT_FAILURE;
+ }
+ }
+
+ qDebug() << "done.";
+ }
+
+ bundleBackup.release();
+
+ if (createDMG) {
+ qDebug() << "creating a DMG disk image...";
+
+ const QString volumeName = QFileInfo(input.outputPath).fileName();
+ const QString imagePath = QString::fromLatin1("%1/%2.dmg")
+ .arg(QFileInfo(bundle).path())
+ .arg(volumeName);
+
+ // no error handling as this is not fatal
+ QProcess p;
+ p.start(QLatin1String("/usr/bin/hdiutil"),
+ QStringList() << QLatin1String("create")
+ << imagePath
+ << QLatin1String("-srcfolder")
+ << bundle
+ << QLatin1String("-ov")
+ << QLatin1String("-volname")
+ << volumeName
+ << QLatin1String("-fs")
+ << QLatin1String("HFS+"));
+ qDebug() << "running " << p.program() << p.arguments();
+ p.waitForFinished(-1);
+ qDebug() << "removing" << bundle;
+ QDir(bundle).removeRecursively();
+ qDebug() << "done.";
+ }
+#else
+ Q_UNUSED(signingIdentity)
+#endif
+ return EXIT_SUCCESS;
+}
+
+QT_BEGIN_NAMESPACE
+int runRcc(int argc, char *argv[]);
+QT_END_NAMESPACE
+
+static int runRcc(const QStringList &args)
+{
+ const int argc = args.count();
+ QVector<char*> argv(argc, nullptr);
+ for (int i = 0; i < argc; ++i)
+ argv[i] = qstrdup(qPrintable(args[i]));
+
+ // Note: this does not run the rcc provided by Qt, this one is using the compiled in binarycreator
+ // version. If it happens that resource mapping fails, we might need to adapt the code here...
+ const int result = runRcc(argc, argv.data());
+
+ foreach (char *arg, argv)
+ delete [] arg;
+
+ return result;
+}
+
+static QSharedPointer<QInstaller::Resource> createDefaultResourceFile(const QString &directory,
+ const QString &binaryName)
+{
+ QTemporaryFile projectFile(directory + QLatin1String("/rccprojectXXXXXX.qrc"));
+ if (!projectFile.open())
+ throw Error(QString::fromLatin1("Cannot create temporary file for generated rcc project file"));
+ projectFile.close();
+
+ const WorkingDirectoryChange wd(directory);
+ const QString projectFileName = QFileInfo(projectFile.fileName()).absoluteFilePath();
+
+ // 1. create the .qrc file
+ if (runRcc(QStringList() << QLatin1String("rcc") << QLatin1String("-project") << QLatin1String("-o")
+ << projectFileName) != EXIT_SUCCESS) {
+ throw Error(QString::fromLatin1("Cannot create rcc project file."));
+ }
+
+ // 2. create the binary resource file from the .qrc file
+ if (runRcc(QStringList() << QLatin1String("rcc") << QLatin1String("-binary") << QLatin1String("-o")
+ << binaryName << projectFileName) != EXIT_SUCCESS) {
+ throw Error(QString::fromLatin1("Cannot compile rcc project file."));
+ }
+
+ return QSharedPointer<QInstaller::Resource>(new QInstaller::Resource(binaryName, binaryName
+ .toUtf8()));
+}
+
+static
+QList<QSharedPointer<QInstaller::Resource> > createBinaryResourceFiles(const QStringList &resources)
+{
+ QList<QSharedPointer<QInstaller::Resource> > result;
+ foreach (const QString &resource, resources) {
+ QFile file(resource);
+ if (file.exists()) {
+ const QString binaryName = generateTemporaryFileName();
+ const QString fileName = QFileInfo(file.fileName()).absoluteFilePath();
+ const int status = runRcc(QStringList() << QLatin1String("rcc")
+ << QLatin1String("-binary") << QLatin1String("-o") << binaryName << fileName);
+ if (status != EXIT_SUCCESS)
+ continue;
+
+ result.append(QSharedPointer<QInstaller::Resource> (new QInstaller::Resource(binaryName,
+ binaryName.toUtf8())));
+ }
+ }
+ return result;
+}
+
+void QInstallerTools::copyConfigData(const QString &configFile, const QString &targetDir)
+{
+ qDebug() << "Begin to copy configuration file and data.";
+
+ const QString sourceConfigFile = QFileInfo(configFile).absoluteFilePath();
+ const QString targetConfigFile = targetDir + QLatin1String("/config.xml");
+ QInstallerTools::copyWithException(sourceConfigFile, targetConfigFile, QLatin1String("configuration"));
+
+ QFile configXml(targetConfigFile);
+ QInstaller::openForRead(&configXml);
+
+ QDomDocument dom;
+ dom.setContent(&configXml);
+ configXml.close();
+
+ // iterate over all child elements, searching for relative file names
+ const QDomNodeList children = dom.documentElement().childNodes();
+ const QString sourceConfigFilePath = QFileInfo(sourceConfigFile).absolutePath();
+ for (int i = 0; i < children.count(); ++i) {
+ QDomElement domElement = children.at(i).toElement();
+ if (domElement.isNull())
+ continue;
+
+ const QString tagName = domElement.tagName();
+ const QString elementText = domElement.text();
+ qDebug().noquote() << QString::fromLatin1("Read dom element: <%1>%2</%1>.").arg(tagName, elementText);
+
+ if (tagName == QLatin1String("ProductImages")) {
+ const QDomNodeList childNodes = domElement.childNodes();
+ for (int i = 0; i < childNodes.count(); ++i) {
+ const QDomElement childElement = childNodes.at(i).toElement();
+ const QString childName = childElement.tagName();
+ if (childName != QLatin1String("Image"))
+ continue;
+
+ const QString targetFile = targetDir + QLatin1Char('/') + childElement.text();
+ const QFileInfo childFileInfo = QFileInfo(sourceConfigFilePath, childElement.text());
+ QInstallerTools::copyWithException(childFileInfo.absoluteFilePath(), targetFile, childName);
+ }
+ continue;
+ }
+
+ QString newName = domElement.text().replace(QRegExp(QLatin1String("\\\\|/|\\.|:")),
+ QLatin1String("_"));
+
+ QString targetFile;
+ QFileInfo elementFileInfo;
+ if (tagName == QLatin1String("Icon") || tagName == QLatin1String("InstallerApplicationIcon")) {
+#if defined(Q_OS_MACOS)
+ const QString suffix = QLatin1String(".icns");
+#elif defined(Q_OS_WIN)
+ const QString suffix = QLatin1String(".ico");
+#else
+ const QString suffix = QLatin1String(".png");
+#endif
+ elementFileInfo = QFileInfo(sourceConfigFilePath, elementText + suffix);
+ targetFile = targetDir + QLatin1Char('/') + newName + suffix;
+ } else {
+ elementFileInfo = QFileInfo(sourceConfigFilePath, elementText);
+ const QString suffix = elementFileInfo.completeSuffix();
+ if (!suffix.isEmpty())
+ newName.append(QLatin1Char('.') + suffix);
+ targetFile = targetDir + QLatin1Char('/') + newName;
+ }
+ if (!elementFileInfo.exists() || elementFileInfo.isDir())
+ continue;
+
+ domElement.replaceChild(dom.createTextNode(newName), domElement.firstChild());
+ QInstallerTools::copyWithException(elementFileInfo.absoluteFilePath(), targetFile, tagName);
+ }
+
+ QInstaller::openForWrite(&configXml);
+ QTextStream stream(&configXml);
+ dom.save(stream, 4);
+
+ qDebug() << "done.\n";
+}
+
+int QInstallerTools::createBinary(BinaryCreatorArgs args, QString &argumentError)
+{
+ // increase maximum numbers of file descriptors
+#if defined (Q_OS_MACOS)
+ struct rlimit rl;
+ getrlimit(RLIMIT_NOFILE, &rl);
+ rl.rlim_cur = qMin(static_cast<rlim_t>(OPEN_MAX), rl.rlim_max);
+ setrlimit(RLIMIT_NOFILE, &rl);
+#endif
+ QString suffix;
+#ifdef Q_OS_WIN
+ suffix = QLatin1String(".exe");
+ if (!args.target.endsWith(suffix))
+ args.target = args.target + suffix;
+#endif
+
+ // Begin check arguments
+ foreach (const QString &packageDir, args.packagesDirectories) {
+ if (!QFileInfo(packageDir).exists()) {
+ argumentError = QString::fromLatin1("Error: Package directory not found at the specified location.");
+ return EXIT_FAILURE;
+ }
+ }
+ foreach (const QString &repositoryDir, args.repositoryDirectories) {
+ if (!QFileInfo(repositoryDir).exists()) {
+ argumentError = QString::fromLatin1("Error: Only local filesystem repositories now supported.");
+ return EXIT_FAILURE;
+ }
+ }
+ if (!args.filteredPackages.isEmpty() && args.onlineOnly) {
+ argumentError = QString::fromLatin1("Error: 'online-only' option cannot be used "
+ "in conjunction with the 'include' or 'exclude' option. An 'online-only' installer will never "
+ "contain any components apart from the root component.");
+ return EXIT_FAILURE;
+ }
+#ifdef Q_OS_WIN
+ if (!args.templateBinary.endsWith(suffix))
+ args.templateBinary = args.templateBinary + suffix;
+#endif
+ if (!QFileInfo(args.templateBinary).exists()) {
+ argumentError = QString::fromLatin1("Error: Template not found at the specified location.");
+ return EXIT_FAILURE;
+ }
+ const QFileInfo fi(args.configFile);
+ if (!fi.exists()) {
+ argumentError = QString::fromLatin1("Error: Config file %1 not found at the "
+ "specified location.").arg(fi.absoluteFilePath());
+ return EXIT_FAILURE;
+ }
+ if (!fi.isFile()) {
+ argumentError = QString::fromLatin1("Error: Configuration %1 is not a file.")
+ .arg(fi.absoluteFilePath());
+ return EXIT_FAILURE;
+ }
+ if (!fi.isReadable()) {
+ argumentError = QString::fromLatin1("Error: Config file %1 is not readable.")
+ .arg(fi.absoluteFilePath());
+ return EXIT_FAILURE;
+ }
+ if (args.onlineOnly && args.offlineOnly) {
+ argumentError = QString::fromLatin1("You cannot use --online-only and "
+ "--offline-only at the same time.");
+ return EXIT_FAILURE;
+ }
+ if (args.target.isEmpty() && !args.compileResource) {
+ argumentError = QString::fromLatin1("Error: Target parameter missing.");
+ return EXIT_FAILURE;
+ }
+ if (args.configFile.isEmpty()) {
+ argumentError = QString::fromLatin1("Error: No configuration file selected.");
+ return EXIT_FAILURE;
+ }
+ if (args.packagesDirectories.isEmpty() && args.repositoryDirectories.isEmpty()) {
+ argumentError = QString::fromLatin1("Error: Both Package directory and Repository parameters missing.");
+ return EXIT_FAILURE;
+ }
+ if (args.onlineOnly) {
+ args.filteredPackages.append(QLatin1String("X_fake_filter_component_for_online_only_installer_X"));
+ args.ftype = QInstallerTools::Include;
+ }
+ // End check arguments
+ qDebug() << "Parsed arguments, ok.";
+
+ Input input;
+ int exitCode = EXIT_FAILURE;
+ QTemporaryDir tmp;
+ tmp.setAutoRemove(false);
+ const QString tmpMetaDir = tmp.path();
+ QTemporaryDir tmp2;
+ tmp2.setAutoRemove(false);
+ const QString tmpRepoDir = tmp2.path();
+ try {
+ const Settings settings = Settings::fromFileAndPrefix(args.configFile, QFileInfo(args.configFile)
+ .absolutePath());
+
+ // Note: the order here is important
+
+ PackageInfoVector packages;
+
+ // 1; update the list of available compressed packages
+ if (!args.repositoryDirectories.isEmpty()) {
+ // 1.1; search packages
+ PackageInfoVector precompressedPackages = createListOfRepositoryPackages(args.repositoryDirectories,
+ &args.filteredPackages, args.ftype);
+ // 1.2; add to common vector
+ packages.append(precompressedPackages);
+ }
+
+ // 2; update the list of available prepared packages
+ if (!args.packagesDirectories.isEmpty()) {
+ // 2.1; search packages
+ PackageInfoVector preparedPackages = createListOfPackages(args.packagesDirectories,
+ &args.filteredPackages, args.ftype);
+ // 2.2; copy the packages data and setup the packages vector with the files we copied,
+ // must happen before copying meta data because files will be compressed if
+ // needed and meta data generation relies on this
+ copyComponentData(args.packagesDirectories, tmpRepoDir, &preparedPackages);
+ // 2.3; add to common vector
+ packages.append(preparedPackages);
+ }
+
+ // 3; copy the meta data of the available packages, generate Updates.xml
+ copyMetaData(tmpMetaDir, tmpRepoDir, packages, settings
+ .applicationName(), settings.version(), QStringList());
+
+ // 4; copy the configuration file and and icons etc.
+ copyConfigData(args.configFile, tmpMetaDir + QLatin1String("/installer-config"));
+ {
+ QSettings confInternal(tmpMetaDir + QLatin1String("/config/config-internal.ini")
+ , QSettings::IniFormat);
+ // assume offline installer if there are no repositories and no
+ //--online-only not set
+ args.offlineOnly = args.offlineOnly | settings.repositories().isEmpty();
+ if (args.onlineOnly)
+ args.offlineOnly = !args.onlineOnly;
+ confInternal.setValue(QLatin1String("offlineOnly"), args.offlineOnly);
+ }
+
+#ifdef Q_OS_MACOS
+ // on mac, we enforce building a bundle
+ if (!args.target.endsWith(QLatin1String(".app")) && !args.target.endsWith(QLatin1String(".dmg")))
+ args.target += QLatin1String(".app");
+#endif
+ if (!args.compileResource) {
+ // 5; put the copied resources into a resource file
+ ResourceCollection metaCollection("QResources");
+ metaCollection.appendResource(createDefaultResourceFile(tmpMetaDir,
+ generateTemporaryFileName()));
+ metaCollection.appendResources(createBinaryResourceFiles(args.resources));
+ input.manager.insertCollection(metaCollection);
+
+ input.packages = packages;
+ input.outputPath = args.target;
+ input.installerExePath = args.templateBinary;
+
+ qDebug() << "Creating the binary";
+ exitCode = assemble(input, settings, args.signingIdentity);
+ } else {
+ createDefaultResourceFile(tmpMetaDir, QDir::currentPath() + QLatin1String("/update.rcc"));
+ exitCode = EXIT_SUCCESS;
+ }
+ } catch (const Error &e) {
+ QFile::remove(input.outputPath);
+ std::cerr << "Caught exception: " << e.message() << std::endl;
+ } catch (...) {
+ QFile::remove(input.outputPath);
+ std::cerr << "Unknown exception caught" << std::endl;
+ }
+
+ qDebug() << "Cleaning up...";
+ const ResourceCollection collection = input.manager.collectionByName("QResources");
+ foreach (const QSharedPointer<QInstaller::Resource> &resource, collection.resources())
+ QFile::remove(QString::fromUtf8(resource->name()));
+ QInstaller::removeDirectory(tmpMetaDir, true);
+ QInstaller::removeDirectory(tmpRepoDir, true);
+
+ return exitCode;
+}
diff --git a/src/libs/ifwtools/binarycreator.h b/src/libs/ifwtools/binarycreator.h
new file mode 100644
index 000000000..c20e230a4
--- /dev/null
+++ b/src/libs/ifwtools/binarycreator.h
@@ -0,0 +1,123 @@
+/**************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef BINARYCREATOR_H
+#define BINARYCREATOR_H
+
+#include "ifwtools_global.h"
+
+#include "repositorygen.h"
+#include "fileutils.h"
+#include "binaryformat.h"
+
+#include <QtCore/QString>
+#include <QtCore/QFile>
+
+namespace QInstallerTools {
+
+struct Input
+{
+ QString outputPath;
+ QString installerExePath;
+ QInstallerTools::PackageInfoVector packages;
+ QInstaller::ResourceCollectionManager manager;
+};
+
+struct IFWTOOLS_EXPORT BinaryCreatorArgs
+{
+ QString target;
+ QString configFile;
+ QString templateBinary;
+ QStringList packagesDirectories;
+ QStringList repositoryDirectories;
+ bool onlineOnly = false;
+ bool offlineOnly = false;
+ QStringList resources;
+ QStringList filteredPackages;
+ FilterType ftype = QInstallerTools::Exclude;
+ bool compileResource = false;
+ QString signingIdentity;
+};
+
+class BundleBackup
+{
+public:
+ explicit BundleBackup(const QString &bundle = QString())
+ : bundle(bundle)
+ {
+ if (!bundle.isEmpty() && QFileInfo(bundle).exists()) {
+ backup = QInstaller::generateTemporaryFileName(bundle);
+ QFile::rename(bundle, backup);
+ }
+ }
+
+ ~BundleBackup()
+ {
+ if (!backup.isEmpty()) {
+ QInstaller::removeDirectory(bundle);
+ QFile::rename(backup, bundle);
+ }
+ }
+
+ void release() const
+ {
+ if (!backup.isEmpty())
+ QInstaller::removeDirectory(backup);
+ backup.clear();
+ }
+
+private:
+ const QString bundle;
+ mutable QString backup;
+};
+
+class WorkingDirectoryChange
+{
+public:
+ explicit WorkingDirectoryChange(const QString &path)
+ : oldPath(QDir::currentPath())
+ {
+ QDir::setCurrent(path);
+ }
+
+ virtual ~WorkingDirectoryChange()
+ {
+ QDir::setCurrent(oldPath);
+ }
+
+private:
+ const QString oldPath;
+};
+
+void copyConfigData(const QString &configFile, const QString &targetDir);
+
+int IFWTOOLS_EXPORT createBinary(BinaryCreatorArgs args, QString &argumentError);
+
+} // namespace QInstallerTools
+
+#endif // BINARYCREATOR_H
diff --git a/src/libs/ifwtools/ifwtools.pri b/src/libs/ifwtools/ifwtools.pri
new file mode 100644
index 000000000..0ce7b45fc
--- /dev/null
+++ b/src/libs/ifwtools/ifwtools.pri
@@ -0,0 +1,19 @@
+INCLUDEPATH += $$PWD $$PWD/rcc
+
+DEFINES += BUILD_SHARED_IFWTOOLS
+
+HEADERS += $$PWD/rcc/rcc.h \
+ $$PWD/rcc/qcorecmdlineargs_p.h
+
+SOURCES += $$PWD/rcc/rcc.cpp \
+ $$PWD/rcc/rccmain.cpp
+
+HEADERS += $$PWD/ifwtools_global.h \
+ $$PWD/repositorygen.h \
+ $$PWD/binarycreator.h
+
+SOURCES += $$PWD/repositorygen.cpp \
+ $$PWD/binarycreator.cpp
+
+RESOURCES += $$PWD/resources/ifwtools.qrc
+
diff --git a/src/libs/ifwtools/ifwtools_global.h b/src/libs/ifwtools/ifwtools_global.h
new file mode 100644
index 000000000..ff04efce8
--- /dev/null
+++ b/src/libs/ifwtools/ifwtools_global.h
@@ -0,0 +1,44 @@
+/**************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef IFWTOOLS_GLOBAL_H
+#define IFWTOOLS_GLOBAL_H
+
+#include <QtCore/QtGlobal>
+
+#ifndef QT_STATIC
+# ifdef BUILD_SHARED_IFWTOOLS
+# define IFWTOOLS_EXPORT Q_DECL_EXPORT
+# else
+# define IFWTOOLS_EXPORT Q_DECL_IMPORT
+# endif
+#else
+# define IFWTOOLS_EXPORT
+#endif
+
+#endif //IFWTOOLS_GLOBAL_H
diff --git a/src/libs/ifwtools/rcc/qcorecmdlineargs_p.h b/src/libs/ifwtools/rcc/qcorecmdlineargs_p.h
new file mode 100644
index 000000000..9c55a43cd
--- /dev/null
+++ b/src/libs/ifwtools/rcc/qcorecmdlineargs_p.h
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QCORECMDLINEARGS_P_H
+#define QCORECMDLINEARGS_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+static inline QStringList qCmdLineArgs(int argc, char *argv[])
+{
+ QStringList args;
+ for (int i = 0; i != argc; ++i)
+ args += QString::fromLocal8Bit(argv[i]);
+ return args;
+}
+
+
+QT_END_NAMESPACE
+
+#endif // QCORECMDLINEARGS_WIN_P_H
diff --git a/src/libs/ifwtools/rcc/rcc.cpp b/src/libs/ifwtools/rcc/rcc.cpp
new file mode 100644
index 000000000..10b7cbc4f
--- /dev/null
+++ b/src/libs/ifwtools/rcc/rcc.cpp
@@ -0,0 +1,1038 @@
+/**************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "rcc.h"
+
+#include <QtCore/QByteArray>
+#include <QtCore/QDateTime>
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QDirIterator>
+#include <QtCore/QFile>
+#include <QtCore/QIODevice>
+#include <QtCore/QLocale>
+#include <QtCore/QRegExp>
+#include <QtCore/QStack>
+
+#include <QXmlStreamReader>
+
+QT_BEGIN_NAMESPACE
+
+enum {
+ CONSTANT_USENAMESPACE = 1,
+ CONSTANT_COMPRESSLEVEL_DEFAULT = -1,
+ CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70
+};
+
+
+#define writeString(s) write(s, sizeof(s))
+
+void RCCResourceLibrary::write(const char *str, int len)
+{
+ --len; // trailing \0 on string literals...
+ int n = m_out.size();
+ m_out.resize(n + len);
+ memcpy(m_out.data() + n, str, len);
+}
+
+void RCCResourceLibrary::writeByteArray(const QByteArray &other)
+{
+ m_out.append(other);
+}
+
+static inline QString msgOpenReadFailed(const QString &fname, const QString &why)
+{
+ return QString::fromUtf8("Unable to open %1 for reading: %2\n").arg(fname).arg(why);
+}
+
+
+///////////////////////////////////////////////////////////
+//
+// RCCFileInfo
+//
+///////////////////////////////////////////////////////////
+
+class RCCFileInfo
+{
+public:
+ enum Flags
+ {
+ NoFlags = 0x00,
+ Compressed = 0x01,
+ Directory = 0x02
+ };
+
+ RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(),
+ QLocale::Language language = QLocale::C,
+ QLocale::Country country = QLocale::AnyCountry,
+ uint flags = NoFlags,
+ int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT,
+ int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT);
+ ~RCCFileInfo();
+
+ QString resourceName() const;
+
+public:
+ qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage);
+ qint64 writeDataName(RCCResourceLibrary &, qint64 offset);
+ void writeDataInfo(RCCResourceLibrary &lib);
+
+ int m_flags;
+ QString m_name;
+ QLocale::Language m_language;
+ QLocale::Country m_country;
+ QFileInfo m_fileInfo;
+ RCCFileInfo *m_parent;
+ QHash<QString, RCCFileInfo*> m_children;
+ int m_compressLevel;
+ int m_compressThreshold;
+
+ qint64 m_nameOffset;
+ qint64 m_dataOffset;
+ qint64 m_childOffset;
+};
+
+RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo,
+ QLocale::Language language, QLocale::Country country, uint flags,
+ int compressLevel, int compressThreshold)
+{
+ m_name = name;
+ m_fileInfo = fileInfo;
+ m_language = language;
+ m_country = country;
+ m_flags = flags;
+ m_parent = nullptr;
+ m_nameOffset = 0;
+ m_dataOffset = 0;
+ m_childOffset = 0;
+ m_compressLevel = compressLevel;
+ m_compressThreshold = compressThreshold;
+}
+
+RCCFileInfo::~RCCFileInfo()
+{
+ qDeleteAll(m_children);
+}
+
+QString RCCFileInfo::resourceName() const
+{
+ QString resource = m_name;
+ for (RCCFileInfo *p = m_parent; p; p = p->m_parent)
+ resource = resource.prepend(p->m_name + QLatin1Char('/'));
+ return QLatin1Char(':') + resource;
+}
+
+void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib)
+{
+ const bool text = (lib.m_format == RCCResourceLibrary::C_Code);
+ //some info
+ if (text) {
+ if (m_language != QLocale::C) {
+ lib.writeString(" // ");
+ lib.writeByteArray(resourceName().toLocal8Bit());
+ lib.writeString(" [");
+ lib.writeByteArray(QByteArray::number(m_country));
+ lib.writeString("::");
+ lib.writeByteArray(QByteArray::number(m_language));
+ lib.writeString("[\n ");
+ } else {
+ lib.writeString(" // ");
+ lib.writeByteArray(resourceName().toLocal8Bit());
+ lib.writeString("\n ");
+ }
+ }
+
+ //pointer data
+ if (m_flags & RCCFileInfo::Directory) {
+ // name offset
+ lib.writeNumber4(m_nameOffset);
+
+ // flags
+ lib.writeNumber2(m_flags);
+
+ // child count
+ lib.writeNumber4(m_children.size());
+
+ // first child offset
+ lib.writeNumber4(m_childOffset);
+ } else {
+ // name offset
+ lib.writeNumber4(m_nameOffset);
+
+ // flags
+ lib.writeNumber2(m_flags);
+
+ // locale
+ lib.writeNumber2(m_country);
+ lib.writeNumber2(m_language);
+
+ //data offset
+ lib.writeNumber4(m_dataOffset);
+ }
+ if (text)
+ lib.writeChar('\n');
+}
+
+qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
+ QString *errorMessage)
+{
+ const bool text = (lib.m_format == RCCResourceLibrary::C_Code);
+
+ //capture the offset
+ m_dataOffset = offset;
+
+ //find the data to be written
+ QFile file(m_fileInfo.absoluteFilePath());
+ if (!file.open(QFile::ReadOnly)) {
+ *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString());
+ return 0;
+ }
+ QByteArray data = file.readAll();
+
+#ifndef QT_NO_COMPRESS
+ // Check if compression is useful for this file
+ if (m_compressLevel != 0 && data.size() != 0) {
+ QByteArray compressed =
+ qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel);
+
+ int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size());
+ if (compressRatio >= m_compressThreshold) {
+ data = compressed;
+ m_flags |= Compressed;
+ }
+ }
+#endif // QT_NO_COMPRESS
+
+ // some info
+ if (text) {
+ lib.writeString(" // ");
+ lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit());
+ lib.writeString("\n ");
+ }
+
+ // write the length
+
+ lib.writeNumber4(data.size());
+ if (text)
+ lib.writeString("\n ");
+ offset += 4;
+
+ // write the payload
+ const char *p = data.constData();
+ if (text) {
+ for (int i = data.size(), j = 0; --i >= 0; --j) {
+ lib.writeHex(*p++);
+ if (j == 0) {
+ lib.writeString("\n ");
+ j = 16;
+ }
+ }
+ } else {
+ for (int i = data.size(); --i >= 0; )
+ lib.writeChar(*p++);
+ }
+ offset += data.size();
+
+ // done
+ if (text)
+ lib.writeString("\n ");
+ return offset;
+}
+
+qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset)
+{
+ const bool text = (lib.m_format == RCCResourceLibrary::C_Code);
+
+ // capture the offset
+ m_nameOffset = offset;
+
+ // some info
+ if (text) {
+ lib.writeString(" // ");
+ lib.writeByteArray(m_name.toLocal8Bit());
+ lib.writeString("\n ");
+ }
+
+ // write the length
+ lib.writeNumber2(m_name.length());
+ if (text)
+ lib.writeString("\n ");
+ offset += 2;
+
+ // write the hash
+ lib.writeNumber4(qt_hash(m_name));
+ if (text)
+ lib.writeString("\n ");
+ offset += 4;
+
+ // write the m_name
+ const QChar *unicode = m_name.unicode();
+ for (int i = 0; i < m_name.length(); ++i) {
+ lib.writeNumber2(unicode[i].unicode());
+ if (text && i % 16 == 0)
+ lib.writeString("\n ");
+ }
+ offset += m_name.length()*2;
+
+ // done
+ if (text)
+ lib.writeString("\n ");
+ return offset;
+}
+
+
+///////////////////////////////////////////////////////////
+//
+// RCCResourceLibrary
+//
+///////////////////////////////////////////////////////////
+
+RCCResourceLibrary::Strings::Strings() :
+ TAG_RCC(QLatin1String("RCC")),
+ TAG_RESOURCE(QLatin1String("qresource")),
+ TAG_FILE(QLatin1String("file")),
+ ATTRIBUTE_LANG(QLatin1String("lang")),
+ ATTRIBUTE_PREFIX(QLatin1String("prefix")),
+ ATTRIBUTE_ALIAS(QLatin1String("alias")),
+ ATTRIBUTE_THRESHOLD(QLatin1String("threshold")),
+ ATTRIBUTE_COMPRESS(QLatin1String("compress"))
+{
+}
+
+RCCResourceLibrary::RCCResourceLibrary()
+ : m_root(nullptr),
+ m_format(C_Code),
+ m_verbose(false),
+ m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT),
+ m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT),
+ m_treeOffset(0),
+ m_namesOffset(0),
+ m_dataOffset(0),
+ m_useNameSpace(CONSTANT_USENAMESPACE),
+ m_errorDevice(nullptr)
+{
+ m_out.reserve(30 * 1000 * 1000);
+}
+
+RCCResourceLibrary::~RCCResourceLibrary()
+{
+ delete m_root;
+}
+
+enum RCCXmlTag {
+ RccTag,
+ ResourceTag,
+ FileTag
+};
+
+bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
+ const QString &fname, QString currentPath, bool ignoreErrors)
+{
+ Q_ASSERT(m_errorDevice);
+ const QChar slash = QLatin1Char('/');
+ if (!currentPath.isEmpty() && !currentPath.endsWith(slash))
+ currentPath += slash;
+
+ QXmlStreamReader reader(inputDevice);
+ QStack<RCCXmlTag> tokens;
+
+ QString prefix;
+ QLocale::Language language = QLocale::c().language();
+ QLocale::Country country = QLocale::c().country();
+ QString alias;
+ int compressLevel = m_compressLevel;
+ int compressThreshold = m_compressThreshold;
+
+ while (!reader.atEnd()) {
+ QXmlStreamReader::TokenType t = reader.readNext();
+ switch (t) {
+ case QXmlStreamReader::StartElement:
+ if (reader.name() == m_strings.TAG_RCC) {
+ if (!tokens.isEmpty())
+ reader.raiseError(QLatin1String("expected <RCC> tag"));
+ else
+ tokens.push(RccTag);
+ } else if (reader.name() == m_strings.TAG_RESOURCE) {
+ if (tokens.isEmpty() || tokens.top() != RccTag) {
+ reader.raiseError(QLatin1String("unexpected <RESOURCE> tag"));
+ } else {
+ tokens.push(ResourceTag);
+
+ QXmlStreamAttributes attributes = reader.attributes();
+ language = QLocale::c().language();
+ country = QLocale::c().country();
+
+ if (attributes.hasAttribute(m_strings.ATTRIBUTE_LANG)) {
+ QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString();
+ QLocale lang = QLocale(attribute);
+ language = lang.language();
+ if (2 == attribute.length()) {
+ // Language only
+ country = QLocale::AnyCountry;
+ } else {
+ country = lang.country();
+ }
+ }
+
+ prefix.clear();
+ if (attributes.hasAttribute(m_strings.ATTRIBUTE_PREFIX))
+ prefix = attributes.value(m_strings.ATTRIBUTE_PREFIX).toString();
+ if (!prefix.startsWith(slash))
+ prefix.prepend(slash);
+ if (!prefix.endsWith(slash))
+ prefix += slash;
+ }
+ } else if (reader.name() == m_strings.TAG_FILE) {
+ if (tokens.isEmpty() || tokens.top() != ResourceTag) {
+ reader.raiseError(QLatin1String("unexpected <FILE> tag"));
+ } else {
+ tokens.push(FileTag);
+
+ QXmlStreamAttributes attributes = reader.attributes();
+ alias.clear();
+ if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS))
+ alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString();
+
+ compressLevel = m_compressLevel;
+ if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS))
+ compressLevel = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString().toInt();
+
+ compressThreshold = m_compressThreshold;
+ if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD))
+ compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt();
+
+ // Special case for -no-compress. Overrides all other settings.
+ if (m_compressLevel == -2)
+ compressLevel = 0;
+ }
+ } else {
+ reader.raiseError(QString(QLatin1String("unexpected tag: %1")).arg(reader.name().toString()));
+ }
+ break;
+
+ case QXmlStreamReader::EndElement:
+ if (reader.name() == m_strings.TAG_RCC) {
+ if (!tokens.isEmpty() && tokens.top() == RccTag)
+ tokens.pop();
+ else
+ reader.raiseError(QLatin1String("unexpected closing tag"));
+ } else if (reader.name() == m_strings.TAG_RESOURCE) {
+ if (!tokens.isEmpty() && tokens.top() == ResourceTag)
+ tokens.pop();
+ else
+ reader.raiseError(QLatin1String("unexpected closing tag"));
+ } else if (reader.name() == m_strings.TAG_FILE) {
+ if (!tokens.isEmpty() && tokens.top() == FileTag)
+ tokens.pop();
+ else
+ reader.raiseError(QLatin1String("unexpected closing tag"));
+ }
+ break;
+
+ case QXmlStreamReader::Characters:
+ if (reader.isWhitespace())
+ break;
+ if (tokens.isEmpty() || tokens.top() != FileTag) {
+ reader.raiseError(QLatin1String("unexpected text"));
+ } else {
+ QString fileName = reader.text().toString();
+ if (fileName.isEmpty()) {
+ const QString msg = QString::fromLatin1("RCC: Warning: Null node in XML of '%1'\n").arg(fname);
+ m_errorDevice->write(msg.toUtf8());
+ }
+
+ if (alias.isNull())
+ alias = fileName;
+
+ alias = QDir::cleanPath(alias);
+ while (alias.startsWith(QLatin1String("../")))
+ alias.remove(0, 3);
+ alias = QDir::cleanPath(m_resourceRoot) + prefix + alias;
+
+ QString absFileName = fileName;
+ if (QDir::isRelativePath(absFileName))
+ absFileName.prepend(currentPath);
+ QFileInfo file(absFileName);
+ if (!file.exists()) {
+ m_failedResources.push_back(absFileName);
+ const QString msg = QString::fromLatin1("RCC: Error in '%1': Cannot find file '%2'\n").arg(fname).arg(fileName);
+ m_errorDevice->write(msg.toUtf8());
+ if (ignoreErrors)
+ continue;
+ else
+ return false;
+ } else if (file.isFile()) {
+ const bool arc =
+ addFile(alias,
+ RCCFileInfo(alias.section(slash, -1),
+ file,
+ language,
+ country,
+ RCCFileInfo::NoFlags,
+ compressLevel,
+ compressThreshold)
+ );
+ if (!arc)
+ m_failedResources.push_back(absFileName);
+ } else {
+ QDir dir;
+ if (file.isDir()) {
+ dir.setPath(file.filePath());
+ } else {
+ dir.setPath(file.path());
+ dir.setNameFilters(QStringList(file.fileName()));
+ if (alias.endsWith(file.fileName()))
+ alias = alias.left(alias.length()-file.fileName().length());
+ }
+ if (!alias.endsWith(slash))
+ alias += slash;
+ QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ it.next();
+ QFileInfo child(it.fileInfo());
+ if (child.fileName() != QLatin1String(".") && child.fileName() != QLatin1String("..")) {
+ const bool arc =
+ addFile(alias + child.fileName(),
+ RCCFileInfo(child.fileName(),
+ child,
+ language,
+ country,
+ child.isDir() ? RCCFileInfo::Directory : RCCFileInfo::NoFlags,
+ compressLevel,
+ compressThreshold)
+ );
+ if (!arc)
+ m_failedResources.push_back(child.fileName());
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (reader.hasError()) {
+ if (ignoreErrors)
+ return true;
+ int errorLine = reader.lineNumber();
+ int errorColumn = reader.columnNumber();
+ QString errorMessage = reader.errorString();
+ QString msg = QString::fromLatin1("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMessage);
+ m_errorDevice->write(msg.toUtf8());
+ return false;
+ }
+
+ if (m_root == nullptr) {
+ const QString msg = QString::fromUtf8("RCC: Warning: No resources in '%1'.\n").arg(fname);
+ m_errorDevice->write(msg.toUtf8());
+ if (!ignoreErrors && m_format == Binary) {
+ // create dummy entry, otherwise loading with QResource will crash
+ m_root = new RCCFileInfo(QString(), QFileInfo(),
+ QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory);
+ }
+ }
+
+ return true;
+}
+
+bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file)
+{
+ Q_ASSERT(m_errorDevice);
+ if (file.m_fileInfo.size() > 0xffffffff) {
+ const QString msg = QString::fromUtf8("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath());
+ m_errorDevice->write(msg.toUtf8());
+ return false;
+ }
+ if (!m_root)
+ m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory);
+
+ RCCFileInfo *parent = m_root;
+ const QStringList nodes = alias.split(QLatin1Char('/'));
+ for (int i = 1; i < nodes.size()-1; ++i) {
+ const QString node = nodes.at(i);
+ if (node.isEmpty())
+ continue;
+ if (!parent->m_children.contains(node)) {
+ RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory);
+ s->m_parent = parent;
+ parent->m_children.insert(node, s);
+ parent = s;
+ } else {
+ parent = parent->m_children[node];
+ }
+ }
+
+ const QString filename = nodes.at(nodes.size()-1);
+ RCCFileInfo *s = new RCCFileInfo(file);
+ s->m_parent = parent;
+ if (parent->m_children.contains(filename)) {
+ foreach (const QString &fileName, m_fileNames)
+ qWarning("%s: Warning: potential duplicate alias detected: '%s'",
+ qPrintable(fileName), qPrintable(filename));
+ }
+ parent->m_children.insertMulti(filename, s);
+ return true;
+}
+
+void RCCResourceLibrary::reset()
+{
+ if (m_root) {
+ delete m_root;
+ m_root = nullptr;
+ }
+ m_errorDevice = nullptr;
+ m_failedResources.clear();
+}
+
+
+bool RCCResourceLibrary::readFiles(bool ignoreErrors, QIODevice &errorDevice)
+{
+ reset();
+ m_errorDevice = &errorDevice;
+ //read in data
+ if (m_verbose) {
+ const QString msg = QString::fromUtf8("Processing %1 files [%2]\n")
+ .arg(m_fileNames.size()).arg(static_cast<int>(ignoreErrors));
+ m_errorDevice->write(msg.toUtf8());
+ }
+ for (int i = 0; i < m_fileNames.size(); ++i) {
+ QFile fileIn;
+ QString fname = m_fileNames.at(i);
+ QString pwd;
+ if (fname == QLatin1String("-")) {
+ fname = QLatin1String("(stdin)");
+ pwd = QDir::currentPath();
+ fileIn.setFileName(fname);
+ if (!fileIn.open(stdin, QIODevice::ReadOnly)) {
+ m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8());
+ return false;
+ }
+ } else {
+ pwd = QFileInfo(fname).path();
+ fileIn.setFileName(fname);
+ if (!fileIn.open(QIODevice::ReadOnly)) {
+ m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8());
+ return false;
+ }
+ }
+ if (m_verbose) {
+ const QString msg = QString::fromUtf8("Interpreting %1\n").arg(fname);
+ m_errorDevice->write(msg.toUtf8());
+ }
+
+ if (!interpretResourceFile(&fileIn, fname, pwd, ignoreErrors))
+ return false;
+ }
+ return true;
+}
+
+QStringList RCCResourceLibrary::dataFiles() const
+{
+ QStringList ret;
+ QStack<RCCFileInfo*> pending;
+
+ if (!m_root)
+ return ret;
+ pending.push(m_root);
+ while (!pending.isEmpty()) {
+ RCCFileInfo *file = pending.pop();
+ for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin();
+ it != file->m_children.end(); ++it) {
+ RCCFileInfo *child = it.value();
+ if (child->m_flags & RCCFileInfo::Directory)
+ pending.push(child);
+ ret.append(child->m_fileInfo.filePath());
+ }
+ }
+ return ret;
+}
+
+// Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion
+static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m)
+{
+ typedef QHash<QString, RCCFileInfo*>::const_iterator ChildConstIterator;
+ const QChar slash = QLatin1Char('/');
+ const ChildConstIterator cend = m_root->m_children.constEnd();
+ for (ChildConstIterator it = m_root->m_children.constBegin(); it != cend; ++it) {
+ const RCCFileInfo *child = it.value();
+ QString childName = path;
+ childName += slash;
+ childName += child->m_name;
+ if (child->m_flags & RCCFileInfo::Directory) {
+ resourceDataFileMapRecursion(child, childName, m);
+ } else {
+ m.insert(childName, child->m_fileInfo.filePath());
+ }
+ }
+}
+
+RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const
+{
+ ResourceDataFileMap rc;
+ if (m_root)
+ resourceDataFileMapRecursion(m_root, QString(QLatin1Char(':')), rc);
+ return rc;
+}
+
+bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &errorDevice)
+{
+ m_errorDevice = &errorDevice;
+ //write out
+ if (m_verbose)
+ m_errorDevice->write("Outputting code\n");
+ if (!writeHeader()) {
+ m_errorDevice->write("Cannot write header\n");
+ return false;
+ }
+ if (m_root) {
+ if (!writeDataBlobs()) {
+ m_errorDevice->write("Cannot write data blobs.\n");
+ return false;
+ }
+ if (!writeDataNames()) {
+ m_errorDevice->write("Cannot write file names\n");
+ return false;
+ }
+ if (!writeDataStructure()) {
+ m_errorDevice->write("Cannot write data tree\n");
+ return false;
+ }
+ }
+ if (!writeInitializer()) {
+ m_errorDevice->write("Cannot write footer\n");
+ return false;
+ }
+ outDevice.write(m_out.constData(), m_out.size());
+ return true;
+}
+
+void RCCResourceLibrary::writeHex(quint8 tmp)
+{
+ const char digits[] = "0123456789abcdef";
+ writeChar('0');
+ writeChar('x');
+ if (tmp < 16) {
+ writeChar(digits[tmp]);
+ } else {
+ writeChar(digits[tmp >> 4]);
+ writeChar(digits[tmp & 0xf]);
+ }
+ writeChar(',');
+}
+
+void RCCResourceLibrary::writeNumber2(quint16 number)
+{
+ if (m_format == RCCResourceLibrary::Binary) {
+ writeChar(number >> 8);
+ writeChar(number);
+ } else {
+ writeHex(number >> 8);
+ writeHex(number);
+ }
+}
+
+void RCCResourceLibrary::writeNumber4(quint32 number)
+{
+ if (m_format == RCCResourceLibrary::Binary) {
+ writeChar(number >> 24);
+ writeChar(number >> 16);
+ writeChar(number >> 8);
+ writeChar(number);
+ } else {
+ writeHex(number >> 24);
+ writeHex(number >> 16);
+ writeHex(number >> 8);
+ writeHex(number);
+ }
+}
+
+bool RCCResourceLibrary::writeHeader()
+{
+ if (m_format == C_Code) {
+ writeString("/****************************************************************************\n");
+ writeString("** Resource object code\n");
+ writeString("**\n");
+ writeString("** Created: ");
+ writeByteArray(QDateTime::currentDateTime().toString().toLatin1());
+ writeString("\n** by: The Resource Compiler for Qt version ");
+ writeByteArray(QT_VERSION_STR);
+ writeString("\n**\n");
+ writeString("** WARNING! All changes made in this file will be lost!\n");
+ writeString( "*****************************************************************************/\n\n");
+ writeString("#include <QtCore/qglobal.h>\n\n");
+ } else if (m_format == Binary) {
+ writeString("qres");
+ writeNumber4(0);
+ writeNumber4(0);
+ writeNumber4(0);
+ writeNumber4(0);
+ }
+ return true;
+}
+
+bool RCCResourceLibrary::writeDataBlobs()
+{
+ Q_ASSERT(m_errorDevice);
+ if (m_format == C_Code)
+ writeString("static const unsigned char qt_resource_data[] = {\n");
+ else if (m_format == Binary)
+ m_dataOffset = m_out.size();
+ QStack<RCCFileInfo*> pending;
+
+ if (!m_root)
+ return false;
+
+ pending.push(m_root);
+ qint64 offset = 0;
+ QString errorMessage;
+ while (!pending.isEmpty()) {
+ RCCFileInfo *file = pending.pop();
+ for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin();
+ it != file->m_children.end(); ++it) {
+ RCCFileInfo *child = it.value();
+ if (child->m_flags & RCCFileInfo::Directory)
+ pending.push(child);
+ else {
+ offset = child->writeDataBlob(*this, offset, &errorMessage);
+ if (offset == 0) {
+ m_errorDevice->write(errorMessage.toUtf8());
+ return false;
+ }
+ }
+ }
+ }
+ if (m_format == C_Code)
+ writeString("\n};\n\n");
+ return true;
+}
+
+bool RCCResourceLibrary::writeDataNames()
+{
+ if (m_format == C_Code)
+ writeString("static const unsigned char qt_resource_name[] = {\n");
+ else if (m_format == Binary)
+ m_namesOffset = m_out.size();
+
+ QHash<QString, int> names;
+ QStack<RCCFileInfo*> pending;
+
+ if (!m_root)
+ return false;
+
+ pending.push(m_root);
+ qint64 offset = 0;
+ while (!pending.isEmpty()) {
+ RCCFileInfo *file = pending.pop();
+ for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin();
+ it != file->m_children.end(); ++it) {
+ RCCFileInfo *child = it.value();
+ if (child->m_flags & RCCFileInfo::Directory)
+ pending.push(child);
+ if (names.contains(child->m_name)) {
+ child->m_nameOffset = names.value(child->m_name);
+ } else {
+ names.insert(child->m_name, offset);
+ offset = child->writeDataName(*this, offset);
+ }
+ }
+ }
+ if (m_format == C_Code)
+ writeString("\n};\n\n");
+ return true;
+}
+
+static bool qt_rcc_compare_hash(const RCCFileInfo *left, const RCCFileInfo *right)
+{
+ return qt_hash(left->m_name) < qt_hash(right->m_name);
+}
+
+bool RCCResourceLibrary::writeDataStructure()
+{
+ if (m_format == C_Code)
+ writeString("static const unsigned char qt_resource_struct[] = {\n");
+ else if (m_format == Binary)
+ m_treeOffset = m_out.size();
+ QStack<RCCFileInfo*> pending;
+
+ if (!m_root)
+ return false;
+
+ //calculate the child offsets (flat)
+ pending.push(m_root);
+ int offset = 1;
+ while (!pending.isEmpty()) {
+ RCCFileInfo *file = pending.pop();
+ file->m_childOffset = offset;
+
+ //sort by hash value for binary lookup
+ QList<RCCFileInfo*> m_children = file->m_children.values();
+ qSort(m_children.begin(), m_children.end(), qt_rcc_compare_hash);
+
+ //write out the actual data now
+ for (int i = 0; i < m_children.size(); ++i) {
+ RCCFileInfo *child = m_children.at(i);
+ ++offset;
+ if (child->m_flags & RCCFileInfo::Directory)
+ pending.push(child);
+ }
+ }
+
+ //write out the structure (ie iterate again!)
+ pending.push(m_root);
+ m_root->writeDataInfo(*this);
+ while (!pending.isEmpty()) {
+ RCCFileInfo *file = pending.pop();
+
+ //sort by hash value for binary lookup
+ QList<RCCFileInfo*> m_children = file->m_children.values();
+ qSort(m_children.begin(), m_children.end(), qt_rcc_compare_hash);
+
+ //write out the actual data now
+ for (int i = 0; i < m_children.size(); ++i) {
+ RCCFileInfo *child = m_children.at(i);
+ child->writeDataInfo(*this);
+ if (child->m_flags & RCCFileInfo::Directory)
+ pending.push(child);
+ }
+ }
+ if (m_format == C_Code)
+ writeString("\n};\n\n");
+
+ return true;
+}
+
+void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name)
+{
+ if (m_useNameSpace) {
+ writeString("QT_MANGLE_NAMESPACE(");
+ writeByteArray(name);
+ writeChar(')');
+ } else {
+ writeByteArray(name);
+ }
+}
+
+void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name)
+{
+ if (m_useNameSpace) {
+ writeString("QT_PREPEND_NAMESPACE(");
+ writeByteArray(name);
+ writeChar(')');
+ } else {
+ writeByteArray(name);
+ }
+}
+
+bool RCCResourceLibrary::writeInitializer()
+{
+ if (m_format == C_Code) {
+ //write("\nQT_BEGIN_NAMESPACE\n");
+ QString initName = m_initName;
+ if (!initName.isEmpty()) {
+ initName.prepend(QLatin1Char('_'));
+ initName.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), QLatin1String("_"));
+ }
+
+ //init
+ if (m_useNameSpace)
+ writeString("QT_BEGIN_NAMESPACE\n\n");
+ if (m_root) {
+ writeString("extern Q_CORE_EXPORT bool qRegisterResourceData\n "
+ "(int, const unsigned char *, "
+ "const unsigned char *, const unsigned char *);\n\n");
+ writeString("extern Q_CORE_EXPORT bool qUnregisterResourceData\n "
+ "(int, const unsigned char *, "
+ "const unsigned char *, const unsigned char *);\n\n");
+ }
+ if (m_useNameSpace)
+ writeString("QT_END_NAMESPACE\n\n\n");
+ QString initResources = QLatin1String("qInitResources");
+ initResources += initName;
+ writeString("int ");
+ writeMangleNamespaceFunction(initResources.toLatin1());
+ writeString("()\n{\n");
+
+ if (m_root) {
+ writeString(" ");
+ writeAddNamespaceFunction("qRegisterResourceData");
+ writeString("\n (0x01, qt_resource_struct, "
+ "qt_resource_name, qt_resource_data);\n");
+ }
+ writeString(" return 1;\n");
+ writeString("}\n\n");
+ writeString("Q_CONSTRUCTOR_FUNCTION(");
+ writeMangleNamespaceFunction(initResources.toLatin1());
+ writeString(")\n\n");
+
+ //cleanup
+ QString cleanResources = QLatin1String("qCleanupResources");
+ cleanResources += initName;
+ writeString("int ");
+ writeMangleNamespaceFunction(cleanResources.toLatin1());
+ writeString("()\n{\n");
+ if (m_root) {
+ writeString(" ");
+ writeAddNamespaceFunction("qUnregisterResourceData");
+ writeString("\n (0x01, qt_resource_struct, "
+ "qt_resource_name, qt_resource_data);\n");
+ }
+ writeString(" return 1;\n");
+ writeString("}\n\n");
+ writeString("Q_DESTRUCTOR_FUNCTION(");
+ writeMangleNamespaceFunction(cleanResources.toLatin1());
+ writeString(")\n\n");
+ } else if (m_format == Binary) {
+ int i = 4;
+ char *p = m_out.data();
+ p[i++] = 0; // 0x01
+ p[i++] = 0;
+ p[i++] = 0;
+ p[i++] = 1;
+
+ p[i++] = (m_treeOffset >> 24) & 0xff;
+ p[i++] = (m_treeOffset >> 16) & 0xff;
+ p[i++] = (m_treeOffset >> 8) & 0xff;
+ p[i++] = (m_treeOffset >> 0) & 0xff;
+
+ p[i++] = (m_dataOffset >> 24) & 0xff;
+ p[i++] = (m_dataOffset >> 16) & 0xff;
+ p[i++] = (m_dataOffset >> 8) & 0xff;
+ p[i++] = (m_dataOffset >> 0) & 0xff;
+
+ p[i++] = (m_namesOffset >> 24) & 0xff;
+ p[i++] = (m_namesOffset >> 16) & 0xff;
+ p[i++] = (m_namesOffset >> 8) & 0xff;
+ p[i++] = (m_namesOffset >> 0) & 0xff;
+ }
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/libs/ifwtools/rcc/rcc.h b/src/libs/ifwtools/rcc/rcc.h
new file mode 100644
index 000000000..ecf408dbc
--- /dev/null
+++ b/src/libs/ifwtools/rcc/rcc.h
@@ -0,0 +1,140 @@
+/**************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef RCC_H
+#define RCC_H
+
+#include <QtCore/QStringList>
+#include <QtCore/QHash>
+#include <QtCore/QString>
+
+QT_BEGIN_NAMESPACE
+
+class RCCFileInfo;
+class QIODevice;
+class QTextStream;
+
+
+class RCCResourceLibrary
+{
+ RCCResourceLibrary(const RCCResourceLibrary &);
+ RCCResourceLibrary &operator=(const RCCResourceLibrary &);
+
+public:
+ RCCResourceLibrary();
+ ~RCCResourceLibrary();
+
+ bool output(QIODevice &out, QIODevice &errorDevice);
+
+ bool readFiles(bool ignoreErrors, QIODevice &errorDevice);
+
+ enum Format { Binary, C_Code };
+ void setFormat(Format f) { m_format = f; }
+ Format format() const { return m_format; }
+
+ void setInputFiles(const QStringList &files) { m_fileNames = files; }
+ QStringList inputFiles() const { return m_fileNames; }
+
+ QStringList dataFiles() const;
+
+ // Return a map of resource identifier (':/newPrefix/images/p1.png') to file.
+ typedef QHash<QString, QString> ResourceDataFileMap;
+ ResourceDataFileMap resourceDataFileMap() const;
+
+ void setVerbose(bool b) { m_verbose = b; }
+ bool verbose() const { return m_verbose; }
+
+ void setInitName(const QString &name) { m_initName = name; }
+ QString initName() const { return m_initName; }
+
+ void setCompressLevel(int c) { m_compressLevel = c; }
+ int compressLevel() const { return m_compressLevel; }
+
+ void setCompressThreshold(int t) { m_compressThreshold = t; }
+ int compressThreshold() const { return m_compressThreshold; }
+
+ void setResourceRoot(const QString &root) { m_resourceRoot = root; }
+ QString resourceRoot() const { return m_resourceRoot; }
+
+ void setUseNameSpace(bool v) { m_useNameSpace = v; }
+ bool useNameSpace() const { return m_useNameSpace; }
+
+ QStringList failedResources() const { return m_failedResources; }
+
+private:
+ struct Strings {
+ Strings();
+ const QString TAG_RCC;
+ const QString TAG_RESOURCE;
+ const QString TAG_FILE;
+ const QString ATTRIBUTE_LANG;
+ const QString ATTRIBUTE_PREFIX;
+ const QString ATTRIBUTE_ALIAS;
+ const QString ATTRIBUTE_THRESHOLD;
+ const QString ATTRIBUTE_COMPRESS;
+ };
+ friend class RCCFileInfo;
+ void reset();
+ bool addFile(const QString &alias, const RCCFileInfo &file);
+ bool interpretResourceFile(QIODevice *inputDevice, const QString &file,
+ QString currentPath = QString(), bool ignoreErrors = false);
+ bool writeHeader();
+ bool writeDataBlobs();
+ bool writeDataNames();
+ bool writeDataStructure();
+ bool writeInitializer();
+ void writeMangleNamespaceFunction(const QByteArray &name);
+ void writeAddNamespaceFunction(const QByteArray &name);
+ void writeHex(quint8 number);
+ void writeNumber2(quint16 number);
+ void writeNumber4(quint32 number);
+ void writeChar(char c) { m_out.append(c); }
+ void writeByteArray(const QByteArray &);
+ void write(const char *, int len);
+
+ const Strings m_strings;
+ RCCFileInfo *m_root;
+ QStringList m_fileNames;
+ QString m_resourceRoot;
+ QString m_initName;
+ Format m_format;
+ bool m_verbose;
+ int m_compressLevel;
+ int m_compressThreshold;
+ int m_treeOffset;
+ int m_namesOffset;
+ int m_dataOffset;
+ bool m_useNameSpace;
+ QStringList m_failedResources;
+ QIODevice *m_errorDevice;
+ QByteArray m_out;
+};
+
+QT_END_NAMESPACE
+
+#endif // RCC_H
diff --git a/src/libs/ifwtools/rcc/rccmain.cpp b/src/libs/ifwtools/rcc/rccmain.cpp
new file mode 100644
index 000000000..47d8fa8de
--- /dev/null
+++ b/src/libs/ifwtools/rcc/rccmain.cpp
@@ -0,0 +1,239 @@
+/**************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "rcc.h"
+#include "qcorecmdlineargs_p.h"
+
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QFileInfo>
+#include <QTextStream>
+
+QT_BEGIN_NAMESPACE
+
+void showHelp(const QString &argv0, const QString &error)
+{
+ fprintf(stderr, "Qt resource compiler\n");
+ if (!error.isEmpty())
+ fprintf(stderr, "%s: %s\n", qPrintable(argv0), qPrintable(error));
+ fprintf(stderr, "Usage: %s [options] <inputs>\n\n"
+ "Options:\n"
+ " -o file write output to file rather than stdout\n"
+ " -name name create an external initialization function with name\n"
+ " -threshold level threshold to consider compressing files\n"
+ " -compress level compress input files by level\n"
+ " -root path prefix resource access path with root path\n"
+ " -no-compress disable all compression\n"
+ " -binary output a binary file for use as a dynamic resource\n"
+ " -namespace turn off namespace macros\n"
+ " -project Output a resource file containing all\n"
+ " files from the current directory\n"
+ " -version display version\n"
+ " -help display this information\n",
+ qPrintable(argv0));
+}
+
+void dumpRecursive(const QDir &dir, QTextStream &out)
+{
+ QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot
+ | QDir::NoSymLinks);
+ foreach (const QFileInfo &entry, entries) {
+ if (entry.isDir()) {
+ dumpRecursive(entry.filePath(), out);
+ } else {
+ out << QLatin1String("<file>")
+ << entry.filePath()
+ << QLatin1String("</file>\n");
+ }
+ }
+}
+
+int createProject(const QString &outFileName)
+{
+ QFile file;
+ bool isOk = false;
+ if (outFileName.isEmpty()) {
+ isOk = file.open(stdout, QFile::WriteOnly | QFile::Text);
+ } else {
+ file.setFileName(outFileName);
+ isOk = file.open(QFile::WriteOnly | QFile::Text);
+ }
+ if (!isOk) {
+ fprintf(stderr, "Unable to open %s: %s\n",
+ outFileName.isEmpty() ? qPrintable(outFileName) : "standard output",
+ qPrintable(file.errorString()));
+ return 1;
+ }
+
+ QTextStream out(&file);
+ out << QLatin1String("<!DOCTYPE RCC><RCC version=\"1.0\">\n"
+ "<qresource>\n");
+
+ // use "." as dir to get relative file paths
+ dumpRecursive(QDir(QLatin1String(".")), out);
+
+ out << QLatin1String("</qresource>\n"
+ "</RCC>\n");
+
+ return 0;
+}
+
+int runRcc(int argc, char *argv[])
+{
+ QString outFilename;
+ bool helpRequested = false;
+ bool list = false;
+ bool projectRequested = false;
+ QStringList filenamesIn;
+
+ QStringList args = qCmdLineArgs(argc, argv);
+
+ RCCResourceLibrary library;
+
+ //parse options
+ QString errorMsg;
+ for (int i = 1; i < args.count() && errorMsg.isEmpty(); i++) {
+ if (args[i].isEmpty())
+ continue;
+ if (args[i][0] == QLatin1Char('-')) { // option
+ QString opt = args[i];
+ if (opt == QLatin1String("-o")) {
+ if (!(i < argc-1)) {
+ errorMsg = QLatin1String("Missing output name");
+ break;
+ }
+ outFilename = args[++i];
+ } else if (opt == QLatin1String("-name")) {
+ if (!(i < argc-1)) {
+ errorMsg = QLatin1String("Missing target name");
+ break;
+ }
+ library.setInitName(args[++i]);
+ } else if (opt == QLatin1String("-root")) {
+ if (!(i < argc-1)) {
+ errorMsg = QLatin1String("Missing root path");
+ break;
+ }
+ library.setResourceRoot(QDir::cleanPath(args[++i]));
+ if (library.resourceRoot().isEmpty()
+ || library.resourceRoot().at(0) != QLatin1Char('/'))
+ errorMsg = QLatin1String("Root must start with a /");
+ } else if (opt == QLatin1String("-compress")) {
+ if (!(i < argc-1)) {
+ errorMsg = QLatin1String("Missing compression level");
+ break;
+ }
+ library.setCompressLevel(args[++i].toInt());
+ } else if (opt == QLatin1String("-threshold")) {
+ if (!(i < argc-1)) {
+ errorMsg = QLatin1String("Missing compression threshold");
+ break;
+ }
+ library.setCompressThreshold(args[++i].toInt());
+ } else if (opt == QLatin1String("-binary")) {
+ library.setFormat(RCCResourceLibrary::Binary);
+ } else if (opt == QLatin1String("-namespace")) {
+ library.setUseNameSpace(!library.useNameSpace());
+ } else if (opt == QLatin1String("-verbose")) {
+ library.setVerbose(true);
+ } else if (opt == QLatin1String("-list")) {
+ list = true;
+ } else if (opt == QLatin1String("-version") || opt == QLatin1String("-v")) {
+ fprintf(stderr, "Qt Resource Compiler version %s\n", QT_VERSION_STR);
+ return 1;
+ } else if (opt == QLatin1String("-help") || opt == QLatin1String("-h")) {
+ helpRequested = true;
+ } else if (opt == QLatin1String("-no-compress")) {
+ library.setCompressLevel(-2);
+ } else if (opt == QLatin1String("-project")) {
+ projectRequested = true;
+ } else {
+ errorMsg = QString::fromLatin1("Unknown option: '%1'").arg(args[i]);
+ }
+ } else {
+ if (!QFile::exists(args[i])) {
+ qWarning("%s: File does not exist '%s'",
+ qPrintable(args[0]), qPrintable(args[i]));
+ return 1;
+ }
+ filenamesIn.append(args[i]);
+ }
+ }
+
+ if (projectRequested && !helpRequested) {
+ return createProject(outFilename);
+ }
+
+ if (!filenamesIn.size() || !errorMsg.isEmpty() || helpRequested) {
+ showHelp(args[0], errorMsg);
+ return 1;
+ }
+ QFile errorDevice;
+ errorDevice.open(stderr, QIODevice::WriteOnly|QIODevice::Text);
+
+ if (library.verbose())
+ errorDevice.write("Qt resource compiler\n");
+
+ library.setInputFiles(filenamesIn);
+
+ if (!library.readFiles(list, errorDevice))
+ return 1;
+
+ // open output
+ QFile out;
+ QIODevice::OpenMode mode = QIODevice::WriteOnly;
+ if (library.format() == RCCResourceLibrary::C_Code)
+ mode |= QIODevice::Text;
+
+ if (outFilename.isEmpty() || outFilename == QLatin1String("-")) {
+ // using this overload close() only flushes.
+ out.open(stdout, mode);
+ } else {
+ out.setFileName(outFilename);
+ if (!out.open(mode)) {
+ const QString msg = QString::fromUtf8("Unable to open %1 for writing: %2\n").arg(outFilename).arg(out.errorString());
+ errorDevice.write(msg.toUtf8());
+ return 1;
+ }
+ }
+
+ // do the task
+ if (list) {
+ const QStringList data = library.dataFiles();
+ for (int i = 0; i < data.size(); ++i) {
+ out.write(qPrintable(QDir::cleanPath(data.at(i))));
+ out.write("\n");
+ }
+ return 0;
+ }
+
+ return library.output(out, errorDevice) ? 0 : 1;
+}
+
+QT_END_NAMESPACE
diff --git a/src/libs/ifwtools/repositorygen.cpp b/src/libs/ifwtools/repositorygen.cpp
new file mode 100644
index 000000000..819ab1ac8
--- /dev/null
+++ b/src/libs/ifwtools/repositorygen.cpp
@@ -0,0 +1,966 @@
+/**************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#include "repositorygen.h"
+
+#include "constants.h"
+#include "fileio.h"
+#include "fileutils.h"
+#include "errors.h"
+#include "globals.h"
+#include "lib7z_create.h"
+#include "lib7z_extract.h"
+#include "lib7z_facade.h"
+#include "lib7z_list.h"
+#include "settings.h"
+#include "qinstallerglobal.h"
+#include "utils.h"
+#include "scriptengine.h"
+
+#include "updater.h"
+
+#include <QtCore/QDirIterator>
+#include <QtCore/QRegExp>
+
+#include <QtXml/QDomDocument>
+#include <QTemporaryDir>
+
+#include <iostream>
+
+using namespace QInstaller;
+using namespace QInstallerTools;
+
+void QInstallerTools::printRepositoryGenOptions()
+{
+ std::cout << " -p|--packages dir The directory containing the available packages." << std::endl;
+ std::cout << " This entry can be given multiple times." << std::endl;
+ std::cout << " --repository dir The directory containing the available repository." << std::endl;
+ std::cout << " This entry can be given multiple times." << std::endl;
+
+ std::cout << " -e|--exclude p1,...,pn Exclude the given packages." << std::endl;
+ std::cout << " -i|--include p1,...,pn Include the given packages and their dependencies" << std::endl;
+ std::cout << " from the repository." << std::endl;
+
+ std::cout << " --ignore-translations Do not use any translation" << std::endl;
+ std::cout << " --ignore-invalid-packages Ignore all invalid packages instead of aborting." << std::endl;
+ std::cout << " --ignore-invalid-repositories Ignore all invalid repositories instead of aborting." << std::endl;
+}
+
+QString QInstallerTools::makePathAbsolute(const QString &path)
+{
+ if (QFileInfo(path).isRelative())
+ return QDir::current().absoluteFilePath(path);
+ return path;
+}
+
+void QInstallerTools::copyWithException(const QString &source, const QString &target, const QString &kind)
+{
+ qDebug() << "Copying associated" << kind << "file" << source;
+
+ const QFileInfo targetFileInfo(target);
+ if (!targetFileInfo.dir().exists())
+ QInstaller::mkpath(targetFileInfo.absolutePath());
+
+ QFile sourceFile(source);
+ if (!sourceFile.copy(target)) {
+ qDebug() << "failed!\n";
+ throw QInstaller::Error(QString::fromLatin1("Cannot copy the %1 file from \"%2\" to \"%3\": "
+ "%4").arg(kind, QDir::toNativeSeparators(source), QDir::toNativeSeparators(target),
+ /* in case of an existing target the error String does not show the file */
+ (targetFileInfo.exists() ? QLatin1String("Target already exist.") : sourceFile.errorString())));
+ }
+
+ qDebug() << "done.";
+}
+
+static QStringList copyFilesFromNode(const QString &parentNode, const QString &childNode, const QString &attr,
+ const QString &kind, const QDomNode &package, const PackageInfo &info, const QString &targetDir)
+{
+ QStringList copiedFiles;
+ const QDomNodeList nodes = package.firstChildElement(parentNode).childNodes();
+ for (int i = 0; i < nodes.count(); ++i) {
+ const QDomNode node = nodes.at(i);
+ if (node.nodeName() != childNode)
+ continue;
+
+ const QDir dir(QString::fromLatin1("%1/meta").arg(info.directory));
+ const QString filter = attr.isEmpty() ? node.toElement().text() : node.toElement().attribute(attr);
+ const QStringList files = dir.entryList(QStringList(filter), QDir::Files);
+ if (files.isEmpty()) {
+ throw QInstaller::Error(QString::fromLatin1("Cannot find any %1 matching \"%2\" "
+ "while copying %1 of \"%3\".").arg(kind, filter, info.name));
+ }
+
+ foreach (const QString &file, files) {
+ const QString source(QString::fromLatin1("%1/meta/%2").arg(info.directory, file));
+ const QString target(QString::fromLatin1("%1/%2/%3").arg(targetDir, info.name, file));
+ copyWithException(source, target, kind);
+ copiedFiles.append(file);
+ }
+ }
+ return copiedFiles;
+}
+
+void QInstallerTools::copyMetaData(const QString &_targetDir, const QString &metaDataDir,
+ const PackageInfoVector &packages, const QString &appName, const QString &appVersion,
+ const QStringList &uniteMetadatas)
+{
+ const QString targetDir = makePathAbsolute(_targetDir);
+ if (!QFile::exists(targetDir))
+ QInstaller::mkpath(targetDir);
+
+ bool componentMetaExtracted = false;
+ QDomDocument doc;
+ QDomElement root;
+ QFile existingUpdatesXml(QFileInfo(metaDataDir, QLatin1String("Updates.xml")).absoluteFilePath());
+ if (existingUpdatesXml.open(QIODevice::ReadOnly) && doc.setContent(&existingUpdatesXml)) {
+ root = doc.documentElement();
+ // remove entry for this component from existing Updates.xml, if found
+ foreach (const PackageInfo &info, packages) {
+ const QDomNodeList packageNodes = root.childNodes();
+ for (int i = packageNodes.count() - 1; i >= 0; --i) {
+ const QDomNode node = packageNodes.at(i);
+ if (node.nodeName() != QLatin1String("PackageUpdate"))
+ continue;
+ if (node.firstChildElement(QLatin1String("Name")).text() != info.name)
+ continue;
+ root.removeChild(node);
+ }
+ }
+ existingUpdatesXml.close();
+ } else {
+ root = doc.createElement(QLatin1String("Updates"));
+ root.appendChild(doc.createElement(QLatin1String("ApplicationName"))).appendChild(doc
+ .createTextNode(appName));
+ root.appendChild(doc.createElement(QLatin1String("ApplicationVersion"))).appendChild(doc
+ .createTextNode(appVersion));
+ root.appendChild(doc.createElement(QLatin1String("Checksum"))).appendChild(doc
+ .createTextNode(QLatin1String("true")));
+ }
+
+ foreach (const PackageInfo &info, packages) {
+ if (info.metaFile.isEmpty() && info.metaNode.isEmpty()) {
+ if (!QDir(targetDir).mkpath(info.name))
+ throw QInstaller::Error(QString::fromLatin1("Cannot create directory \"%1\".").arg(info.name));
+
+ const QString packageXmlPath = QString::fromLatin1("%1/meta/package.xml").arg(info.directory);
+ qDebug() << "Copy meta data for package" << info.name << "using" << packageXmlPath;
+
+ QFile file(packageXmlPath);
+ QInstaller::openForRead(&file);
+
+ QString errMsg;
+ int line = 0;
+ int column = 0;
+ QDomDocument packageXml;
+ if (!packageXml.setContent(&file, &errMsg, &line, &column)) {
+ throw QInstaller::Error(QString::fromLatin1("Cannot parse \"%1\": line: %2, column: %3: %4 (%5)")
+ .arg(QDir::toNativeSeparators(packageXmlPath)).arg(line).arg(column).arg(errMsg, info.name));
+ }
+
+ QDomElement update = doc.createElement(QLatin1String("PackageUpdate"));
+ QDomNode nameElement = update.appendChild(doc.createElement(QLatin1String("Name")));
+ nameElement.appendChild(doc.createTextNode(info.name));
+
+ // list of current unused or later transformed tags
+ QStringList blackList;
+ blackList << QLatin1String("UserInterfaces") << QLatin1String("Translations") <<
+ QLatin1String("Licenses") << QLatin1String("Name");
+
+ bool foundDefault = false;
+ bool foundVirtual = false;
+ bool foundDisplayName = false;
+ bool foundDownloadableArchives = false;
+ bool foundCheckable = false;
+ const QDomNode package = packageXml.firstChildElement(QLatin1String("Package"));
+ const QDomNodeList childNodes = package.childNodes();
+ for (int i = 0; i < childNodes.count(); ++i) {
+ const QDomNode node = childNodes.at(i);
+ const QString key = node.nodeName();
+
+ if (key == QLatin1String("Default"))
+ foundDefault = true;
+ if (key == QLatin1String("Virtual"))
+ foundVirtual = true;
+ if (key == QLatin1String("DisplayName"))
+ foundDisplayName = true;
+ if (key == QLatin1String("DownloadableArchives"))
+ foundDownloadableArchives = true;
+ if (key == QLatin1String("Checkable"))
+ foundCheckable = true;
+ if (node.isComment() || blackList.contains(key))
+ continue; // just skip comments and some tags...
+
+ QDomElement element = doc.createElement(key);
+ for (int j = 0; j < node.attributes().size(); ++j) {
+ element.setAttribute(node.attributes().item(j).toAttr().name(),
+ node.attributes().item(j).toAttr().value());
+ }
+ update.appendChild(element).appendChild(doc.createTextNode(node.toElement().text()));
+ }
+
+ if (foundDefault && foundVirtual) {
+ throw QInstaller::Error(QString::fromLatin1("Error: <Default> and <Virtual> elements are "
+ "mutually exclusive in file \"%1\".").arg(QDir::toNativeSeparators(packageXmlPath)));
+ }
+
+ if (foundDefault && foundCheckable) {
+ throw QInstaller::Error(QString::fromLatin1("Error: <Default> and <Checkable>"
+ "elements are mutually exclusive in file \"%1\".")
+ .arg(QDir::toNativeSeparators(packageXmlPath)));
+ }
+
+ if (!foundDisplayName) {
+ qWarning() << "No DisplayName tag found at" << info.name << ", using component Name instead.";
+ QDomElement displayNameElement = doc.createElement(QLatin1String("DisplayName"));
+ update.appendChild(displayNameElement).appendChild(doc.createTextNode(info.name));
+ }
+
+ // get the size of the data
+ quint64 componentSize = 0;
+ quint64 compressedComponentSize = 0;
+
+ const QDir::Filters filters = QDir::Files | QDir::NoDotAndDotDot;
+ const QDir dataDir = QString::fromLatin1("%1/%2/data").arg(metaDataDir, info.name);
+ const QFileInfoList entries = dataDir.exists() ? dataDir.entryInfoList(filters | QDir::Dirs)
+ : QDir(QString::fromLatin1("%1/%2").arg(metaDataDir, info.name)).entryInfoList(filters);
+ qDebug() << "calculate size of directory" << dataDir.absolutePath();
+ foreach (const QFileInfo &fi, entries) {
+ try {
+ if (fi.isDir()) {
+ QDirIterator recursDirIt(fi.filePath(), QDirIterator::Subdirectories);
+ while (recursDirIt.hasNext()) {
+ recursDirIt.next();
+ const quint64 size = QInstaller::fileSize(recursDirIt.fileInfo());
+ componentSize += size;
+ compressedComponentSize += size;
+ }
+ } else if (Lib7z::isSupportedArchive(fi.filePath())) {
+ // if it's an archive already, list its files and sum the uncompressed sizes
+ QFile archive(fi.filePath());
+ compressedComponentSize += archive.size();
+ QInstaller::openForRead(&archive);
+
+ QVector<Lib7z::File>::const_iterator fileIt;
+ const QVector<Lib7z::File> files = Lib7z::listArchive(&archive);
+ for (fileIt = files.begin(); fileIt != files.end(); ++fileIt)
+ componentSize += fileIt->uncompressedSize;
+ } else {
+ // otherwise just add its size
+ const quint64 size = QInstaller::fileSize(fi);
+ componentSize += size;
+ compressedComponentSize += size;
+ }
+ } catch (const QInstaller::Error &error) {
+ qDebug().noquote() << error.message();
+ } catch(...) {
+ // ignore, that's just about the sizes - and size doesn't matter, you know?
+ }
+ }
+
+ QDomElement fileElement = doc.createElement(QLatin1String("UpdateFile"));
+ fileElement.setAttribute(QLatin1String("UncompressedSize"), componentSize);
+ fileElement.setAttribute(QLatin1String("CompressedSize"), compressedComponentSize);
+ // adding the OS attribute to be compatible with old sdks
+ fileElement.setAttribute(QLatin1String("OS"), QLatin1String("Any"));
+ update.appendChild(fileElement);
+
+ root.appendChild(update);
+
+ // copy script file
+ const QString script = package.firstChildElement(QLatin1String("Script")).text();
+ if (!script.isEmpty()) {
+ QFile scriptFile(QString::fromLatin1("%1/meta/%2").arg(info.directory, script));
+ if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ throw QInstaller::Error(QString::fromLatin1("Cannot open component script at \"%1\".")
+ .arg(QDir::toNativeSeparators(scriptFile.fileName())));
+ }
+
+ const QString scriptContent = QLatin1String("(function() {")
+ + QString::fromUtf8(scriptFile.readAll())
+ + QLatin1String(";"
+ " if (typeof Component == \"undefined\")"
+ " throw \"Missing Component constructor. Please check your script.\";"
+ "})();");
+
+ // if the user isn't aware of the downloadable archives value we will add it automatically later
+ foundDownloadableArchives |= scriptContent.contains(QLatin1String("addDownloadableArchive"))
+ || scriptContent.contains(QLatin1String("removeDownloadableArchive"));
+
+ static QInstaller::ScriptEngine testScriptEngine;
+ const QJSValue value = testScriptEngine.evaluate(scriptContent, scriptFile.fileName());
+ if (value.isError()) {
+ throw QInstaller::Error(QString::fromLatin1("Exception while loading component "
+ "script at \"%1\": %2").arg(QDir::toNativeSeparators(scriptFile.fileName()),
+ value.toString().isEmpty() ? QString::fromLatin1("Unknown error.") :
+ value.toString() + QStringLiteral(" on line number: ") +
+ value.property(QStringLiteral("lineNumber")).toString()));
+ }
+
+ const QString toLocation(QString::fromLatin1("%1/%2/%3").arg(targetDir, info.name, script));
+ copyWithException(scriptFile.fileName(), toLocation, QInstaller::scScript);
+ }
+
+ // write DownloadableArchives tag if that is missed by the user
+ if (!foundDownloadableArchives && !info.copiedFiles.isEmpty()) {
+ QStringList realContentFiles;
+ foreach (const QString &filePath, info.copiedFiles) {
+ if (!filePath.endsWith(QLatin1String(".sha1"), Qt::CaseInsensitive)) {
+ const QString fileName = QFileInfo(filePath).fileName();
+ // remove unnecessary version string from filename and add it to the list
+ realContentFiles.append(fileName.mid(info.version.count()));
+ }
+ }
+
+ update.appendChild(doc.createElement(QLatin1String("DownloadableArchives"))).appendChild(doc
+ .createTextNode(realContentFiles.join(QChar::fromLatin1(','))));
+ }
+
+ // copy user interfaces
+ const QStringList uiFiles = copyFilesFromNode(QLatin1String("UserInterfaces"),
+ QLatin1String("UserInterface"), QString(), QLatin1String("user interface"), package, info,
+ targetDir);
+ if (!uiFiles.isEmpty()) {
+ update.appendChild(doc.createElement(QLatin1String("UserInterfaces"))).appendChild(doc
+ .createTextNode(uiFiles.join(QChar::fromLatin1(','))));
+ }
+
+ // copy translations
+ QStringList trFiles;
+ if (!qApp->arguments().contains(QString::fromLatin1("--ignore-translations"))) {
+ trFiles = copyFilesFromNode(QLatin1String("Translations"), QLatin1String("Translation"),
+ QString(), QLatin1String("translation"), package, info, targetDir);
+ if (!trFiles.isEmpty()) {
+ update.appendChild(doc.createElement(QLatin1String("Translations"))).appendChild(doc
+ .createTextNode(trFiles.join(QChar::fromLatin1(','))));
+ }
+ }
+
+ // copy license files
+ const QStringList licenses = copyFilesFromNode(QLatin1String("Licenses"), QLatin1String("License"),
+ QLatin1String("file"), QLatin1String("license"), package, info, targetDir);
+ if (!licenses.isEmpty()) {
+ foreach (const QString &trFile, trFiles) {
+ // Copy translated license file based on the assumption that it will have the same base name
+ // as the original license plus the file name of an existing translation file without suffix.
+ foreach (const QString &license, licenses) {
+ const QFileInfo untranslated(license);
+ const QString translatedLicense = QString::fromLatin1("%2_%3.%4").arg(untranslated
+ .baseName(), QFileInfo(trFile).baseName(), untranslated.completeSuffix());
+ // ignore copy failure, that's just about the translations
+ QFile::copy(QString::fromLatin1("%1/meta/%2").arg(info.directory).arg(translatedLicense),
+ QString::fromLatin1("%1/%2/%3").arg(targetDir, info.name, translatedLicense));
+ }
+ }
+ update.appendChild(package.firstChildElement(QLatin1String("Licenses")).cloneNode());
+ }
+ } else {
+ // Extract metadata from archive
+ if (!info.metaFile.isEmpty()){
+ QFile metaFile(info.metaFile);
+ QInstaller::openForRead(&metaFile);
+ Lib7z::extractArchive(&metaFile, targetDir);
+ componentMetaExtracted = true;
+ }
+
+ // Restore "PackageUpdate" node;
+ QDomDocument update;
+ if (!update.setContent(info.metaNode)) {
+ throw QInstaller::Error(QString::fromLatin1("Cannot restore \"PackageUpdate\" description for node %1").arg(info.name));
+ }
+
+ root.appendChild(update.documentElement());
+ }
+ }
+
+ if (!componentMetaExtracted) {
+ foreach (const QString uniteMetadata, uniteMetadatas) {
+ QFile metaFile(QFileInfo(metaDataDir, uniteMetadata).absoluteFilePath());
+ QInstaller::openForRead(&metaFile);
+ Lib7z::extractArchive(&metaFile, targetDir);
+ }
+ }
+
+ doc.appendChild(root);
+
+ QFile targetUpdatesXml(targetDir + QLatin1String("/Updates.xml"));
+ QInstaller::openForWrite(&targetUpdatesXml);
+ QInstaller::blockingWrite(&targetUpdatesXml, doc.toByteArray());
+}
+
+PackageInfoVector QInstallerTools::createListOfPackages(const QStringList &packagesDirectories,
+ QStringList *packagesToFilter, FilterType filterType)
+{
+ qDebug() << "Collecting information about available packages...";
+
+ bool ignoreInvalidPackages = qApp->arguments().contains(QString::fromLatin1("--ignore-invalid-packages"));
+
+ PackageInfoVector dict;
+ QFileInfoList entries;
+ foreach (const QString &packagesDirectory, packagesDirectories)
+ entries.append(QDir(packagesDirectory).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot));
+ for (QFileInfoList::const_iterator it = entries.constBegin(); it != entries.constEnd(); ++it) {
+ if (filterType == Exclude) {
+ // Check for current file in exclude list, if found, skip it and remove it from exclude list
+ if (packagesToFilter->contains(it->fileName())) {
+ packagesToFilter->removeAll(it->fileName());
+ continue;
+ }
+ } else {
+ // Check for current file in include list, if not found, skip it; if found, remove it from include list
+ if (!packagesToFilter->contains(it->fileName()))
+ continue;
+ packagesToFilter->removeAll(it->fileName());
+ }
+ qDebug() << "Found subdirectory" << it->fileName();
+ // because the filter is QDir::Dirs - filename means the name of the subdirectory
+ if (it->fileName().contains(QLatin1Char('-'))) {
+ qDebug("When using the component \"%s\" as a dependency, "
+ "to ensure backward compatibility, you must add a colon symbol at the end, "
+ "even if you do not specify a version.",
+ qUtf8Printable(it->fileName()));
+ }
+
+ QFile file(QString::fromLatin1("%1/meta/package.xml").arg(it->filePath()));
+ QFileInfo fileInfo(file);
+ if (!fileInfo.exists()) {
+ if (ignoreInvalidPackages)
+ continue;
+ throw QInstaller::Error(QString::fromLatin1("Component \"%1\" does not contain a package "
+ "description (meta/package.xml is missing).").arg(QDir::toNativeSeparators(it->fileName())));
+ }
+
+ file.open(QIODevice::ReadOnly);
+
+ QDomDocument doc;
+ QString error;
+ int errorLine = 0;
+ int errorColumn = 0;
+ if (!doc.setContent(&file, &error, &errorLine, &errorColumn)) {
+ if (ignoreInvalidPackages)
+ continue;
+ throw QInstaller::Error(QString::fromLatin1("Component package description in \"%1\" is invalid. "
+ "Error at line: %2, column: %3 -> %4").arg(QDir::toNativeSeparators(fileInfo.absoluteFilePath()),
+ QString::number(errorLine),
+ QString::number(errorColumn), error));
+ }
+
+ const QDomElement packageElement = doc.firstChildElement(QLatin1String("Package"));
+ const QString name = packageElement.firstChildElement(QLatin1String("Name")).text();
+ if (!name.isEmpty() && name != it->fileName()) {
+ qWarning().nospace() << "The <Name> tag in the file " << fileInfo.absoluteFilePath()
+ << " is ignored - the installer uses the path element right before the 'meta'"
+ << " (" << it->fileName() << ")";
+ }
+
+ QString releaseDate = packageElement.firstChildElement(QLatin1String("ReleaseDate")).text();
+ if (releaseDate.isEmpty()) {
+ qWarning("Release date for \"%s\" is empty! Using the current date instead.",
+ qPrintable(fileInfo.absoluteFilePath()));
+ releaseDate = QDate::currentDate().toString(Qt::ISODate);
+ }
+
+ if (!QDate::fromString(releaseDate, Qt::ISODate).isValid()) {
+ if (ignoreInvalidPackages)
+ continue;
+ throw QInstaller::Error(QString::fromLatin1("Release date for \"%1\" is invalid! <ReleaseDate>%2"
+ "</ReleaseDate>. Supported format: YYYY-MM-DD").arg(QDir::toNativeSeparators(fileInfo.absoluteFilePath()),
+ releaseDate));
+ }
+
+ PackageInfo info;
+ info.name = it->fileName();
+ info.version = packageElement.firstChildElement(QLatin1String("Version")).text();
+ // Version cannot start with comparison characters, be an empty string
+ // or have whitespaces at the beginning or at the end
+ if (!QRegExp(QLatin1String("(?![<=>\\s]+)(.+)")).exactMatch(info.version) ||
+ (info.version != info.version.trimmed())) {
+ if (ignoreInvalidPackages)
+ continue;
+ throw QInstaller::Error(QString::fromLatin1("Component version for \"%1\" is invalid! <Version>%2</Version>")
+ .arg(QDir::toNativeSeparators(fileInfo.absoluteFilePath()), info.version));
+ }
+ info.dependencies = packageElement.firstChildElement(QLatin1String("Dependencies")).text()
+ .split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
+ info.directory = it->filePath();
+ dict.push_back(info);
+
+ qDebug() << "- it provides the package" << info.name << " - " << info.version;
+ }
+
+ if (!packagesToFilter->isEmpty() && packagesToFilter->at(0) != QString::fromLatin1(
+ "X_fake_filter_component_for_online_only_installer_X")) {
+ qWarning() << "The following explicitly given packages could not be found\n in package directory:" << *packagesToFilter;
+ }
+
+ if (dict.isEmpty())
+ qDebug() << "No available packages found at the specified location.";
+
+ return dict;
+}
+
+PackageInfoVector QInstallerTools::createListOfRepositoryPackages(const QStringList &repositoryDirectories,
+ QStringList *packagesToFilter, FilterType filterType)
+{
+ qDebug() << "Collecting information about available repository packages...";
+
+ bool ignoreInvalidRepositories = qApp->arguments().contains(QString::fromLatin1("--ignore-invalid-repositories"));
+
+ PackageInfoVector dict;
+ QFileInfoList entries;
+ foreach (const QString &repositoryDirectory, repositoryDirectories)
+ entries.append(QFileInfo(repositoryDirectory));
+ for (QFileInfoList::const_iterator it = entries.constBegin(); it != entries.constEnd(); ++it) {
+
+ qDebug() << "Process repository" << it->fileName();
+
+ QFile file(QString::fromLatin1("%1/Updates.xml").arg(it->filePath()));
+
+ QFileInfo fileInfo(file);
+ if (!fileInfo.exists()) {
+ if (ignoreInvalidRepositories) {
+ qDebug() << "- skip invalid repository";
+ continue;
+ }
+ throw QInstaller::Error(QString::fromLatin1("Repository \"%1\" does not contain a update "
+ "description (Updates.xml is missing).").arg(QDir::toNativeSeparators(it->fileName())));
+ }
+ if (!file.open(QIODevice::ReadOnly)) {
+ qDebug() << "Cannot open Updates.xml for reading:" << file.errorString();
+ continue;
+ }
+
+ QString error;
+ QDomDocument doc;
+ if (!doc.setContent(&file, &error)) {
+ qDebug().nospace() << "Cannot fetch a valid version of Updates.xml from repository "
+ << it->fileName() << ": " << error;
+ continue;
+ }
+ file.close();
+
+ const QDomElement root = doc.documentElement();
+ if (root.tagName() != QLatin1String("Updates")) {
+ throw QInstaller::Error(QCoreApplication::translate("QInstaller",
+ "Invalid content in \"%1\".").arg(QDir::toNativeSeparators(file.fileName())));
+ }
+
+ const QDomNodeList children = root.childNodes();
+ for (int i = 0; i < children.count(); ++i) {
+ const QDomElement el = children.at(i).toElement();
+ if ((!el.isNull()) && (el.tagName() == QLatin1String("PackageUpdate"))) {
+ QInstallerTools::PackageInfo info;
+
+ QDomElement c1 = el.firstChildElement(QInstaller::scName);
+ if (!c1.isNull())
+ info.name = c1.text();
+ else
+ continue;
+ if (filterType == Exclude) {
+ // Check for current package in exclude list, if found, skip it
+ if (packagesToFilter->contains(info.name)) {
+ continue;
+ }
+ } else {
+ // Check for current package in include list, if not found, skip it
+ if (!packagesToFilter->contains(info.name))
+ continue;
+ }
+ c1 = el.firstChildElement(QInstaller::scVersion);
+ if (!c1.isNull())
+ info.version = c1.text();
+ else
+ continue;
+
+ info.directory = QString::fromLatin1("%1/%2").arg(it->filePath(), info.name);
+ const QDomElement sha1 = el.firstChildElement(QInstaller::scSHA1);
+ if (!sha1.isNull()) {
+ info.metaFile = QString::fromLatin1("%1/%3%2").arg(info.directory,
+ QString::fromLatin1("meta.7z"), info.version);
+ }
+
+ const QDomNodeList c2 = el.childNodes();
+ for (int j = 0; j < c2.count(); ++j) {
+ const QDomElement c2Element = c2.at(j).toElement();
+ if (c2Element.tagName() == QInstaller::scDependencies)
+ info.dependencies = c2Element.text()
+ .split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
+ else if (c2Element.tagName() == QInstaller::scDownloadableArchives) {
+ QStringList names = c2Element.text()
+ .split(QInstaller::commaRegExp(), QString::SkipEmptyParts);
+ foreach (const QString &name, names) {
+ info.copiedFiles.append(QString::fromLatin1("%1/%3%2").arg(info.directory,
+ name, info.version));
+ info.copiedFiles.append(QString::fromLatin1("%1/%3%2.sha1").arg(info.directory,
+ name, info.version));
+ }
+ }
+ }
+ QString metaString;
+ {
+ QTextStream metaStream(&metaString);
+ el.save(metaStream, 0);
+ }
+ info.metaNode = metaString;
+ dict.push_back(info);
+ qDebug() << "- it provides the package" << info.name << " - " << info.version;
+ }
+ }
+ }
+
+ return dict;
+}
+
+QHash<QString, QString> QInstallerTools::buildPathToVersionMapping(const PackageInfoVector &info)
+{
+ QHash<QString, QString> map;
+ foreach (const PackageInfo &inf, info)
+ map[inf.name] = inf.version;
+ return map;
+}
+
+static void writeSHA1ToNodeWithName(QDomDocument &doc, QDomNodeList &list, const QByteArray &sha1sum,
+ const QString &nodename = QString())
+{
+ if (nodename.isEmpty())
+ qDebug() << "Writing sha1sum node.";
+ else
+ qDebug() << "Searching sha1sum node for" << nodename;
+ QString sha1Value = QString::fromLatin1(sha1sum.toHex().constData());
+ for (int i = 0; i < list.size(); ++i) {
+ QDomNode curNode = list.at(i);
+ QDomNode nameTag = curNode.firstChildElement(scName);
+ if ((!nameTag.isNull() && nameTag.toElement().text() == nodename) || nodename.isEmpty()) {
+ QDomNode sha1Node = curNode.firstChildElement(scSHA1);
+ QDomNode newSha1Node = doc.createElement(scSHA1);
+ newSha1Node.appendChild(doc.createTextNode(sha1Value));
+
+ if (!sha1Node.isNull() && sha1Node.hasChildNodes()) {
+ QDomNode sha1NodeChild = sha1Node.firstChild();
+ QString sha1OldValue = sha1NodeChild.nodeValue();
+ if (sha1Value == sha1OldValue) {
+ qDebug() << "- keeping the existing sha1sum" << sha1OldValue;
+ continue;
+ } else {
+ qDebug() << "- clearing the old sha1sum" << sha1OldValue;
+ sha1Node.removeChild(sha1NodeChild);
+ }
+ }
+ if (sha1Node.isNull())
+ curNode.appendChild(newSha1Node);
+ else
+ curNode.replaceChild(newSha1Node, sha1Node);
+ qDebug() << "- writing the sha1sum" << sha1Value;
+ }
+ }
+}
+
+void QInstallerTools::compressMetaDirectories(const QString &repoDir, const QString &existingUnite7zUrl,
+ const QHash<QString, QString> &versionMapping, bool createSplitMetadata, bool createUnifiedMetadata)
+{
+ QDomDocument doc;
+ // use existing Updates.xml, if any
+ QFile existingUpdatesXml(QFileInfo(QDir(repoDir), QLatin1String("Updates.xml")).absoluteFilePath());
+ if (!existingUpdatesXml.open(QIODevice::ReadOnly) || !doc.setContent(&existingUpdatesXml)) {
+ qDebug() << "Cannot find Updates.xml";
+ }
+ existingUpdatesXml.close();
+
+ QDir dir(repoDir);
+ const QStringList entryList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+
+ QStringList absPaths;
+ if (createUnifiedMetadata) {
+ absPaths = unifyMetadata(repoDir, existingUnite7zUrl, doc);
+ }
+
+ if (createSplitMetadata) {
+ splitMetadata(entryList, repoDir, doc, versionMapping);
+ } else {
+ // remove the files that got compressed
+ foreach (const QString path, absPaths)
+ QInstaller::removeFiles(path, true);
+ }
+
+ QInstaller::openForWrite(&existingUpdatesXml);
+ QInstaller::blockingWrite(&existingUpdatesXml, doc.toByteArray());
+ existingUpdatesXml.close();
+}
+
+QStringList QInstallerTools::unifyMetadata(const QString &repoDir, const QString &existingRepoDir, QDomDocument doc)
+{
+ QStringList absPaths;
+ QDir dir(repoDir);
+ const QStringList entryList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+
+ foreach (const QString &i, entryList) {
+ dir.cd(i);
+ const QString absPath = dir.absolutePath();
+ absPaths.append(absPath);
+ dir.cdUp();
+ }
+
+ QTemporaryDir existingRepoTempDir;
+ QString existingRepoTemp = existingRepoTempDir.path();
+ if (!existingRepoDir.isEmpty()) {
+ existingRepoTempDir.setAutoRemove(false);
+ QFile archiveFile(existingRepoDir);
+ QInstaller::openForRead(&archiveFile);
+ Lib7z::extractArchive(&archiveFile, existingRepoTemp);
+ QDir dir(existingRepoTemp);
+ QStringList existingRepoEntries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ foreach (const QString existingRepoEntry, existingRepoEntries) {
+ if (entryList.contains(existingRepoEntry)) {
+ continue;
+ } else {
+ dir.cd(existingRepoEntry);
+ const QString absPath = dir.absolutePath();
+ absPaths.append(absPath);
+ }
+ }
+ }
+
+ // Compress all metadata from repository to one single 7z
+ const QString metadataFilename = QDateTime::currentDateTime().
+ toString(QLatin1String("yyyy-MM-dd-hhmm")) + QLatin1String("_meta.7z");
+ const QString tmpTarget = repoDir + QDir::separator() + metadataFilename;
+ Lib7z::createArchive(tmpTarget, absPaths, Lib7z::TmpFile::No);
+
+ QFile tmp(tmpTarget);
+ tmp.open(QFile::ReadOnly);
+ const QByteArray sha1Sum = QInstaller::calculateHash(&tmp, QCryptographicHash::Sha1);
+ QDomNodeList elements = doc.elementsByTagName(QLatin1String("Updates"));
+ writeSHA1ToNodeWithName(doc, elements, sha1Sum, QString());
+
+ qDebug() << "Updating the metadata node with name " << metadataFilename;
+ if (elements.count() > 0) {
+ QDomNode node = elements.at(0);
+ QDomNode nameTag = node.firstChildElement(QLatin1String("MetadataName"));
+
+ QDomNode newNodeTag = doc.createElement(QLatin1String("MetadataName"));
+ newNodeTag.appendChild(doc.createTextNode(metadataFilename));
+
+ if (nameTag.isNull())
+ node.appendChild(newNodeTag);
+ else
+ node.replaceChild(newNodeTag, nameTag);
+ }
+ QInstaller::removeDirectory(existingRepoTemp, true);
+ return absPaths;
+}
+
+void QInstallerTools::splitMetadata(const QStringList &entryList, const QString &repoDir,
+ QDomDocument doc, const QHash<QString, QString> &versionMapping)
+{
+ QStringList absPaths;
+ QDomNodeList elements = doc.elementsByTagName(QLatin1String("PackageUpdate"));
+ QDir dir(repoDir);
+ foreach (const QString &i, entryList) {
+ dir.cd(i);
+ const QString absPath = dir.absolutePath();
+ const QString path = QString(i).remove(repoDir);
+ if (path.isNull())
+ continue;
+ const QString versionPrefix = versionMapping[path];
+ const QString fn = QLatin1String(versionPrefix.toLatin1() + "meta.7z");
+ const QString tmpTarget = repoDir + QLatin1String("/") + fn;
+ Lib7z::createArchive(tmpTarget, QStringList() << absPath, Lib7z::TmpFile::No);
+ // remove the files that got compressed
+ QInstaller::removeFiles(absPath, true);
+ QFile tmp(tmpTarget);
+ tmp.open(QFile::ReadOnly);
+ const QByteArray sha1Sum = QInstaller::calculateHash(&tmp, QCryptographicHash::Sha1);
+ writeSHA1ToNodeWithName(doc, elements, sha1Sum, path);
+ const QString finalTarget = absPath + QLatin1String("/") + fn;
+ if (!tmp.rename(finalTarget)) {
+ throw QInstaller::Error(QString::fromLatin1("Cannot move file \"%1\" to \"%2\".").arg(
+ QDir::toNativeSeparators(tmpTarget), QDir::toNativeSeparators(finalTarget)));
+ }
+ dir.cdUp();
+ }
+}
+
+void QInstallerTools::copyComponentData(const QStringList &packageDirs, const QString &repoDir,
+ PackageInfoVector *const infos)
+{
+ for (int i = 0; i < infos->count(); ++i) {
+ const PackageInfo info = infos->at(i);
+ const QString name = info.name;
+ qDebug() << "Copying component data for" << name;
+
+ const QString namedRepoDir = QString::fromLatin1("%1/%2").arg(repoDir, name);
+ if (!QDir().mkpath(namedRepoDir)) {
+ throw QInstaller::Error(QString::fromLatin1("Cannot create repository directory for component \"%1\".")
+ .arg(name));
+ }
+
+ if (info.copiedFiles.isEmpty()) {
+ QStringList compressedFiles;
+ QStringList filesToCompress;
+ foreach (const QString &packageDir, packageDirs) {
+ const QDir dataDir(QString::fromLatin1("%1/%2/data").arg(packageDir, name));
+ foreach (const QString &entry, dataDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files)) {
+ QFileInfo fileInfo(dataDir.absoluteFilePath(entry));
+ if (fileInfo.isFile() && !fileInfo.isSymLink()) {
+ const QString absoluteEntryFilePath = dataDir.absoluteFilePath(entry);
+ if (Lib7z::isSupportedArchive(absoluteEntryFilePath)) {
+ QFile tmp(absoluteEntryFilePath);
+ QString target = QString::fromLatin1("%1/%3%2").arg(namedRepoDir, entry, info.version);
+ qDebug() << "Copying archive from" << tmp.fileName() << "to" << target;
+ if (!tmp.copy(target)) {
+ throw QInstaller::Error(QString::fromLatin1("Cannot copy file \"%1\" to \"%2\": %3")
+ .arg(QDir::toNativeSeparators(tmp.fileName()), QDir::toNativeSeparators(target), tmp.errorString()));
+ }
+ compressedFiles.append(target);
+ } else {
+ filesToCompress.append(absoluteEntryFilePath);
+ }
+ } else if (fileInfo.isDir()) {
+ qDebug() << "Compressing data directory" << entry;
+ QString target = QString::fromLatin1("%1/%3%2.7z").arg(namedRepoDir, entry, info.version);
+ Lib7z::createArchive(target, QStringList() << dataDir.absoluteFilePath(entry),
+ Lib7z::TmpFile::No);
+ compressedFiles.append(target);
+ } else if (fileInfo.isSymLink()) {
+ filesToCompress.append(dataDir.absoluteFilePath(entry));
+ }
+ }
+ }
+
+ if (!filesToCompress.isEmpty()) {
+ qDebug() << "Compressing files found in data directory:" << filesToCompress;
+ QString target = QString::fromLatin1("%1/%3%2").arg(namedRepoDir, QLatin1String("content.7z"),
+ info.version);
+ Lib7z::createArchive(target, filesToCompress, Lib7z::TmpFile::No);
+ compressedFiles.append(target);
+ }
+
+ foreach (const QString &target, compressedFiles) {
+ (*infos)[i].copiedFiles.append(target);
+
+ QFile archiveFile(target);
+ QFile archiveHashFile(archiveFile.fileName() + QLatin1String(".sha1"));
+
+ qDebug() << "Hash is stored in" << archiveHashFile.fileName();
+ qDebug() << "Creating hash of archive" << archiveFile.fileName();
+
+ try {
+ QInstaller::openForRead(&archiveFile);
+ const QByteArray hashOfArchiveData = QInstaller::calculateHash(&archiveFile,
+ QCryptographicHash::Sha1).toHex();
+ archiveFile.close();
+
+ QInstaller::openForWrite(&archiveHashFile);
+ archiveHashFile.write(hashOfArchiveData);
+ qDebug() << "Generated sha1 hash:" << hashOfArchiveData;
+ (*infos)[i].copiedFiles.append(archiveHashFile.fileName());
+ archiveHashFile.close();
+ } catch (const QInstaller::Error &/*e*/) {
+ archiveFile.close();
+ archiveHashFile.close();
+ throw;
+ }
+ }
+ } else {
+ foreach (const QString &file, (*infos)[i].copiedFiles) {
+ QFileInfo fromInfo(file);
+ QFile from(file);
+ QString target = QString::fromLatin1("%1/%2").arg(namedRepoDir, fromInfo.fileName());
+ qDebug() << "Copying file from" << from.fileName() << "to" << target;
+ if (!from.copy(target)) {
+ throw QInstaller::Error(QString::fromLatin1("Cannot copy file \"%1\" to \"%2\": %3")
+ .arg(QDir::toNativeSeparators(from.fileName()), QDir::toNativeSeparators(target), from.errorString()));
+ }
+ }
+ }
+ }
+}
+
+void QInstallerTools::filterNewComponents(const QString &repositoryDir, QInstallerTools::PackageInfoVector &packages)
+{
+ QDomDocument doc;
+ QFile file(repositoryDir + QLatin1String("/Updates.xml"));
+ if (file.open(QFile::ReadOnly) && doc.setContent(&file)) {
+ const QDomElement root = doc.documentElement();
+ if (root.tagName() != QLatin1String("Updates")) {
+ throw QInstaller::Error(QCoreApplication::translate("QInstaller",
+ "Invalid content in \"%1\".").arg(QDir::toNativeSeparators(file.fileName())));
+ }
+ file.close(); // close the file, we read the content already
+
+ // read the already existing updates xml content
+ const QDomNodeList children = root.childNodes();
+ QHash <QString, QInstallerTools::PackageInfo> hash;
+ for (int i = 0; i < children.count(); ++i) {
+ const QDomElement el = children.at(i).toElement();
+ if ((!el.isNull()) && (el.tagName() == QLatin1String("PackageUpdate"))) {
+ QInstallerTools::PackageInfo info;
+ const QDomNodeList c2 = el.childNodes();
+ for (int j = 0; j < c2.count(); ++j) {
+ const QDomElement c2Element = c2.at(j).toElement();
+ if (c2Element.tagName() == scName)
+ info.name = c2Element.text();
+ else if (c2Element.tagName() == scVersion)
+ info.version = c2Element.text();
+ }
+ hash.insert(info.name, info);
+ }
+ }
+
+ // remove all components that have no update (decision based on the version tag)
+ for (int i = packages.count() - 1; i >= 0; --i) {
+ const QInstallerTools::PackageInfo info = packages.at(i);
+
+ // check if component already exists & version did not change
+ if (hash.contains(info.name) && KDUpdater::compareVersion(info.version, hash.value(info.name).version) < 1) {
+ packages.remove(i); // the version did not change, no need to update the component
+ continue;
+ }
+ qDebug() << "Update component" << info.name << "in"<< repositoryDir << ".";
+ }
+ }
+}
+
+QString QInstallerTools::existingUniteMeta7z(const QString &repositoryDir)
+{
+ QString uniteMeta7z = QString();
+ QFile file(repositoryDir + QLatin1String("/Updates.xml"));
+ QDomDocument doc;
+ if (file.open(QFile::ReadOnly) && doc.setContent(&file)) {
+ QDomNodeList elements = doc.elementsByTagName(QLatin1String("MetadataName"));
+ if (elements.count() > 0 && elements.at(0).isElement()) {
+ uniteMeta7z = elements.at(0).toElement().text();
+ QFile metaFile(repositoryDir + QDir::separator() + uniteMeta7z);
+ if (!metaFile.exists()) {
+ throw QInstaller::Error(QString::fromLatin1("Unite meta7z \"%1\" does not exist in repository \"%2\"")
+ .arg(QDir::toNativeSeparators(metaFile.fileName()), repositoryDir));
+ }
+ }
+ }
+ return uniteMeta7z;
+}
diff --git a/src/libs/ifwtools/repositorygen.h b/src/libs/ifwtools/repositorygen.h
new file mode 100644
index 000000000..49d0a51dd
--- /dev/null
+++ b/src/libs/ifwtools/repositorygen.h
@@ -0,0 +1,88 @@
+/**************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Installer Framework.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+**************************************************************************/
+
+#ifndef REPOSITORYGEN_H
+#define REPOSITORYGEN_H
+
+#include "ifwtools_global.h"
+
+#include <QHash>
+#include <QString>
+#include <QStringList>
+#include <QVector>
+#include <QDomDocument>
+
+namespace QInstallerTools {
+
+struct IFWTOOLS_EXPORT PackageInfo
+{
+ QString name;
+ QString version;
+ QString directory;
+ QStringList dependencies;
+ QStringList copiedFiles;
+ QString metaFile;
+ QString metaNode;
+};
+typedef QVector<PackageInfo> PackageInfoVector;
+
+enum IFWTOOLS_EXPORT FilterType {
+ Include,
+ Exclude
+};
+
+void IFWTOOLS_EXPORT printRepositoryGenOptions();
+QString IFWTOOLS_EXPORT makePathAbsolute(const QString &path);
+void IFWTOOLS_EXPORT copyWithException(const QString &source, const QString &target, const QString &kind = QString());
+
+PackageInfoVector IFWTOOLS_EXPORT createListOfPackages(const QStringList &packagesDirectories, QStringList *packagesToFilter,
+ FilterType ftype);
+
+PackageInfoVector IFWTOOLS_EXPORT createListOfRepositoryPackages(const QStringList &repositoryDirectories, QStringList *packagesToFilter,
+ FilterType filterType);
+
+QHash<QString, QString> IFWTOOLS_EXPORT buildPathToVersionMapping(const PackageInfoVector &info);
+
+void IFWTOOLS_EXPORT compressMetaDirectories(const QString &repoDir, const QString &existingUnite7zUrl,
+ const QHash<QString, QString> &versionMapping, bool createSplitMetadata, bool createUnifiedMetadata);
+
+QStringList unifyMetadata(const QString &repoDir, const QString &existingRepoDir, QDomDocument doc);
+void splitMetadata(const QStringList &entryList, const QString &repoDir, QDomDocument doc,
+ const QHash<QString, QString> &versionMapping);
+
+void IFWTOOLS_EXPORT copyMetaData(const QString &outDir, const QString &dataDir, const PackageInfoVector &packages,
+ const QString &appName, const QString& appVersion, const QStringList &uniteMetadatas);
+void IFWTOOLS_EXPORT copyComponentData(const QStringList &packageDir, const QString &repoDir, PackageInfoVector *const infos);
+
+void IFWTOOLS_EXPORT filterNewComponents(const QString &repositoryDir, QInstallerTools::PackageInfoVector &packages);
+
+QString IFWTOOLS_EXPORT existingUniteMeta7z(const QString &repositoryDir);
+
+} // namespace QInstallerTools
+
+#endif // REPOSITORYGEN_H
diff --git a/src/libs/ifwtools/resources/copylibsintobundle.sh b/src/libs/ifwtools/resources/copylibsintobundle.sh
new file mode 100644
index 000000000..1ad5da4db
--- /dev/null
+++ b/src/libs/ifwtools/resources/copylibsintobundle.sh
@@ -0,0 +1,189 @@
+#!/bin/sh
+#############################################################################
+##
+## Copyright (C) 2017 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the Qt Installer Framework.
+##
+## $QT_BEGIN_LICENSE:GPL-EXCEPT$
+## 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 The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## GNU General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU
+## General Public License version 3 as published by the Free Software
+## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+## included in the packaging of this file. Please review the following
+## information to ensure the GNU General Public License requirements will
+## be met: https://www.gnu.org/licenses/gpl-3.0.html.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+
+# this script puts all libs directly needed by the bundle into it
+
+QTDIR=""
+IS_DEBUG=0
+HAVE_CORE=0
+HAVE_SVG=0
+HAVE_PHONON=0
+HAVE_SCRIPT=0
+HAVE_SQL=0
+HAVE_WEBKIT=0
+
+function handleFile()
+{
+ local FILE=$1
+ local BUNDLE=$2
+
+ # all dynamic libs directly needed by the bundle, which are not in /System/Library or in /usr/lib (which are system default libs, which we don't want)
+ local LIBS=`xcrun otool -L $FILE | grep -v 'executable_path' | grep -v '/System/Library' | grep -v '/usr/lib' | grep '/' | sed -ne 's,^ *\(.*\) (.*,\1,p'`
+
+ local lib
+ for lib in $LIBS; do
+ local NAME=`basename $lib`
+
+ if echo $NAME | grep 'QtCore' >/dev/null; then
+ HAVE_CORE=1
+ QTDIR=`echo $lib | sed -ne 's,^\(.*\)/lib/[^/]*QtCore.*$,\1,p'`
+ if echo $NAME | grep 'debug' >/dev/null; then
+ IS_DEBUG=1
+ fi
+ elif echo $NAME | grep 'QtSvg' >/dev/null; then
+ HAVE_SVG=1
+ elif echo $NAME | grep 'phonon' >/dev/null; then
+ HAVE_PHONON=1
+ elif echo $NAME | grep 'QtScript' >/dev/null; then
+ HAVE_SCRIPT=1
+ elif echo $NAME | grep 'QtSql' >/dev/null; then
+ HAVE_SQL=1
+ elif echo $NAME | grep 'QtWebKit' >/dev/null; then
+ HAVE_WEBKIT=1
+ fi
+
+ if [ `basename $FILE` != $NAME ]; then
+
+ # this part handles libraries which are macOS frameworks
+ if echo $lib | grep '\.framework' >/dev/null; then
+ local FRAMEWORKPATH=`echo $lib | sed -ne 's,\(.*\.framework\).*,\1,p'`
+ local FRAMEWORKNAME=`basename $FRAMEWORKPATH`
+ local NEWFRAMEWORKPATH=`echo $lib | sed -ne "s,.*\($FRAMEWORKNAME\),\1,p"`
+
+ # Qt installed via the precompled binaries...
+ if [ $FRAMEWORKPATH = $FRAMEWORKNAME ]; then
+ FRAMEWORKPATH="/Library/Frameworks/$FRAMEWORKNAME"
+ if [ ! -e "$FRAMEWORKPATH" ]; then
+ echo "Framework $FRAMEWORKNAME not found."
+ exit 1
+ fi
+ fi
+
+ if [ ! -e "$BUNDLE/Contents/Frameworks/$NEWFRAMEWORKPATH" ]; then
+ echo Embedding framework $FRAMEWORKNAME
+
+
+ # copy the framework into the bundle
+ cp -R $FRAMEWORKPATH $BUNDLE/Contents/Frameworks
+ # remove debug libs we've copied
+ find $BUNDLE/Contents/Frameworks/$FRAMEWORKNAME -regex '.*_debug\(\.dSYM\)*' | xargs rm -rf
+
+ handleFile "$BUNDLE/Contents/Frameworks/$NEWFRAMEWORKPATH" "$BUNDLE"
+ fi
+ # and inform the dynamic linker about this
+ xcrun install_name_tool -change $lib @executable_path/../Frameworks/$NEWFRAMEWORKPATH $FILE
+
+
+ # this part handles 'normal' dynamic libraries (.dylib)
+ else
+ if [ ! -e "$BUNDLE/Contents/Frameworks/$NAME" ]; then
+ echo Embedding library $NAME
+
+ # Qt installed via the precompled binaries...
+ if [ $lib = $NAME ]; then
+ lib="/Library/Frameworks/$NAME"
+ if [ ! -e "$lib" ]; then
+ lib="/usr/lib/$NAME"
+ fi
+ if [ ! -e "$lib" ]; then
+ echo "Library $NAME not found."
+ exit 1
+ fi
+ fi
+
+ # copy the lib into the bundle
+ cp $lib $BUNDLE/Contents/Frameworks
+ handleFile "$BUNDLE/Contents/Frameworks/$NAME" "$BUNDLE"
+ fi
+
+ # and inform the dynamic linker about this
+ xcrun install_name_tool -change $lib @executable_path/../Frameworks/$NAME $FILE
+ fi
+
+ fi
+ done
+}
+
+function handleQtPlugins()
+{
+ local PLUGINPATH=$QTDIR/plugins
+
+ # QTDIR was not found, then we're using /Developer/Applications/Qt
+ if [ "$PLUGINPATH" = "/plugins" ]; then
+ PLUGINPATH="/Developer/Applications/Qt/plugins"
+ fi
+
+ CLASS=$1
+ EXECUTABLE=$2
+ BUNDLE=$3
+ mkdir -p $BUNDLE/Contents/plugins/$CLASS
+ echo Add $CLASS plugins
+ for plugin in `ls $PLUGINPATH/$CLASS/*`; do
+ plugin=`basename $plugin`
+ if echo $plugin | grep 'debug' >/dev/null; then
+ #if [ $IS_DEBUG -eq 1 ]; then
+ cp "$PLUGINPATH/$CLASS/$plugin" $BUNDLE/Contents/plugins/$CLASS
+ xcrun install_name_tool -change $plugin @executable_path/../plugins/$CLASS/$plugin $EXECUTABLE
+ handleFile $BUNDLE/Contents/plugins/$CLASS/$plugin $BUNDLE
+ #fi
+ else
+ #if [ $IS_DEBUG -eq 0 ]; then
+ cp "$PLUGINPATH/$CLASS/$plugin" $BUNDLE/Contents/plugins/$CLASS
+ xcrun install_name_tool -change $plugin @executable_path/../plugins/$CLASS/$plugin $EXECUTABLE
+ handleFile $BUNDLE/Contents/plugins/$CLASS/$plugin $BUNDLE
+ #fi
+ fi
+ done
+}
+
+# the app bundle we're working with
+BUNDLE=$1
+# the executable inside of the bundle
+EXECUTABLE=$BUNDLE/Contents/MacOS/`xargs < $BUNDLE/Contents/Info.plist | sed -ne 's,.*<key>CFBundleExecutable</key> <string>\([^<]*\)</string>.*,\1,p'`
+
+mkdir -p $BUNDLE/Contents/Frameworks
+
+handleFile $EXECUTABLE $BUNDLE
+
+if [ $HAVE_CORE -eq 1 ]; then
+ handleQtPlugins "imageformats" "$EXECUTABLE" "$BUNDLE"
+fi
+if [ $HAVE_SVG -eq 1 ]; then
+ handleQtPlugins "iconengines" "$EXECUTABLE" "$BUNDLE"
+fi
+if [ $HAVE_PHONON -eq 1 ]; then
+ handleQtPlugins "phonon_backend" "$EXECUTABLE" "$BUNDLE"
+fi
+if [ $HAVE_SQL -eq 1 ]; then
+ handleQtPlugins "sqldrivers" "$EXECUTABLE" "$BUNDLE"
+fi
+if [ $HAVE_WEBKIT -eq 1 ]; then
+ handleQtPlugins "codecs" "$EXECUTABLE" "$BUNDLE"
+fi
diff --git a/src/libs/ifwtools/resources/default_icon_mac.icns b/src/libs/ifwtools/resources/default_icon_mac.icns
new file mode 100644
index 000000000..8d870d649
--- /dev/null
+++ b/src/libs/ifwtools/resources/default_icon_mac.icns
Binary files differ
diff --git a/src/libs/ifwtools/resources/ifwtools.qrc b/src/libs/ifwtools/resources/ifwtools.qrc
new file mode 100644
index 000000000..8b73a8442
--- /dev/null
+++ b/src/libs/ifwtools/resources/ifwtools.qrc
@@ -0,0 +1,7 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>default_icon_mac.icns</file>
+ <file>copylibsintobundle.sh</file>
+ <file alias="resources/installerbase.ico">../../../sdk/installerbase.ico</file>
+</qresource>
+</RCC>
diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro
index d1e21bb63..2462dbe63 100644
--- a/src/libs/installer/installer.pro
+++ b/src/libs/installer/installer.pro
@@ -6,6 +6,7 @@ CONFIG += staticlib
include(../7zip/7zip.pri)
include(../kdtools/kdtools.pri)
+include(../ifwtools/ifwtools.pri)
include(../../../installerfw.pri)
# productkeycheck API