diff options
author | Arttu Tarkiainen <arttu.tarkiainen@qt.io> | 2020-11-20 17:09:30 +0200 |
---|---|---|
committer | Arttu Tarkiainen <arttu.tarkiainen@qt.io> | 2020-12-02 10:25:18 +0000 |
commit | 885a41d1667ac8ccf59a4de76cb8449074a466ac (patch) | |
tree | 7e0909c5b06754f458c6b6e7d3a77a4ffd994be7 /src/libs/ifwtools/binarycreator.cpp | |
parent | f053b9a627921b03529b4f797a97b582675fbe71 (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/libs/ifwtools/binarycreator.cpp')
-rw-r--r-- | src/libs/ifwtools/binarycreator.cpp | 830 |
1 files changed, 830 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; +} |