mirror of
https://github.com/QuasarApp/installer-framework.git
synced 2025-04-29 23:14:34 +00:00
Follow up on 924ebcdbc9b9e590a5f0905941e2d05ac34d4be2 (Part 1). Get rid of KDSaveFile, reuse QTemporaryFile. After changing KDSaveFile to inherit from QFileDevice, it turns out to be not working properly anymore and crashing e.g. binarycreator while using it. To save fixing it we just switch to QTemporaryFile. Change-Id: I785a04db91b431f5e122f193ecf2ef3c54e34b1c Reviewed-by: Kai Koehne <kai.koehne@digia.com>
799 lines
33 KiB
C++
799 lines
33 KiB
C++
/**************************************************************************
|
|
**
|
|
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
|
|
** Contact: http://www.qt-project.org/legal
|
|
**
|
|
** This file is part of the Qt Installer Framework.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and Digia. For licensing terms and
|
|
** conditions see http://qt.digia.com/licensing. For further information
|
|
** use the contact form at http://qt.digia.com/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 2.1 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** In addition, as a special exception, Digia gives you certain additional
|
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3.0 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU General Public License version 3.0 requirements will be
|
|
** met: http://www.gnu.org/copyleft/gpl.html.
|
|
**
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
**************************************************************************/
|
|
#include "common/repositorygen.h"
|
|
|
|
#include <qtpatch.h>
|
|
|
|
#include <binaryformat.h>
|
|
#include <errors.h>
|
|
#include <fileio.h>
|
|
#include <fileutils.h>
|
|
#include <init.h>
|
|
#include <repository.h>
|
|
#include <settings.h>
|
|
#include <utils.h>
|
|
|
|
#include <QtCore/QDirIterator>
|
|
#include <QtCore/QProcess>
|
|
#include <QtCore/QSettings>
|
|
#include <QtCore/QTemporaryFile>
|
|
#include <QTemporaryDir>
|
|
|
|
#include <iostream>
|
|
|
|
using namespace QInstaller;
|
|
using namespace QInstallerCreator;
|
|
|
|
struct Input {
|
|
QString outputPath;
|
|
QString installerExePath;
|
|
ComponentIndex components;
|
|
QString binaryResourcePath;
|
|
QStringList binaryResources;
|
|
|
|
Range<qint64> operationsPos;
|
|
QVector<Range<qint64> > resourcePos;
|
|
Range<qint64> componentIndexSegment;
|
|
};
|
|
|
|
class BundleBackup
|
|
{
|
|
public:
|
|
explicit BundleBackup(const QString &bundle = QString())
|
|
: bundle(bundle)
|
|
{
|
|
if (!bundle.isEmpty() && QFileInfo(bundle).exists()) {
|
|
backup = generateTemporaryFileName(bundle);
|
|
QFile::rename(bundle, backup);
|
|
}
|
|
}
|
|
|
|
~BundleBackup()
|
|
{
|
|
if (!backup.isEmpty()) {
|
|
removeDirectory(bundle);
|
|
QFile::rename(backup, bundle);
|
|
}
|
|
}
|
|
|
|
void release() const
|
|
{
|
|
if (!backup.isEmpty())
|
|
removeDirectory(backup);
|
|
backup.clear();
|
|
}
|
|
|
|
private:
|
|
const QString bundle;
|
|
mutable QString backup;
|
|
};
|
|
|
|
#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
|
|
|
|
static int assemble(Input input, const QInstaller::Settings &settings)
|
|
{
|
|
#ifdef Q_OS_MAC
|
|
if (QFileInfo(input.installerExePath).isBundle()) {
|
|
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);
|
|
|
|
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 = QFile::exists(settings.icon()) ? settings.icon()
|
|
: 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 SYSTEM \"file://localhost/System/Library/DTDs"
|
|
"/PropertyList.dtd\">") << endl;
|
|
plistStream << QLatin1String("<plist version=\"0.9\">") << endl;
|
|
plistStream << QLatin1String("<dict>") << endl;
|
|
plistStream << QLatin1String(" <key>CFBundleIconFile</key>") << endl;
|
|
plistStream << QLatin1String(" <string>") << iconTargetFile << QLatin1String("</string>")
|
|
<< endl;
|
|
plistStream << QLatin1String(" <key>CFBundlePackageType</key>") << endl;
|
|
plistStream << QLatin1String(" <string>APPL</string>") << endl;
|
|
plistStream << QLatin1String(" <key>CFBundleGetInfoString</key>") << endl;
|
|
#define QUOTE_(x) #x
|
|
#define QUOTE(x) QUOTE_(x)
|
|
plistStream << QLatin1String(" <string>") << QLatin1String(QUOTE(IFW_VERSION)) << ("</string>")
|
|
<< endl;
|
|
#undef QUOTE
|
|
#undef QUOTE_
|
|
plistStream << QLatin1String(" <key>CFBundleSignature</key>") << endl;
|
|
plistStream << QLatin1String(" <string> ???? </string>") << endl;
|
|
plistStream << QLatin1String(" <key>CFBundleExecutable</key>") << endl;
|
|
plistStream << QLatin1String(" <string>") << fi.completeBaseName() << QLatin1String("</string>")
|
|
<< endl;
|
|
plistStream << QLatin1String(" <key>CFBundleIdentifier</key>") << endl;
|
|
plistStream << QLatin1String(" <string>com.yourcompany.installerbase</string>") << endl;
|
|
plistStream << QLatin1String(" <key>NOTE</key>") << endl;
|
|
plistStream << QLatin1String(" <string>This file was generated by Qt Installer Framework.</string>")
|
|
<< endl;
|
|
plistStream << QLatin1String(" <key>NSPrincipalClass</key>") << endl;
|
|
plistStream << QLatin1String(" <string>NSApplication</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("Could not 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("Could not 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());
|
|
} else {
|
|
if (QFile::exists(settings.icon())) {
|
|
setApplicationIcon(tempFile, settings.icon());
|
|
}
|
|
}
|
|
#elif defined(Q_OS_MAC)
|
|
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();
|
|
QFile::rename(input.outputPath, tempFile);
|
|
QFile::remove(copyscript);
|
|
}
|
|
#endif
|
|
|
|
QTemporaryFile out;
|
|
QString targetName = input.outputPath;
|
|
#ifdef Q_OS_MAC
|
|
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("Could not 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_MAC
|
|
if (!exe.copy(input.outputPath)) {
|
|
throw Error(QString::fromLatin1("Could not copy %1 to %2: %3").arg(exe.fileName(),
|
|
input.outputPath, exe.errorString()));
|
|
}
|
|
#else
|
|
QInstaller::openForRead(&exe);
|
|
QInstaller::appendData(&out, &exe, exe.size());
|
|
#endif
|
|
|
|
const qint64 dataBlockStart = out.pos();
|
|
qDebug() << "Data block starts at" << dataBlockStart;
|
|
|
|
// append our self created resource file
|
|
QFile res(input.binaryResourcePath);
|
|
QInstaller::openForRead(&res);
|
|
QInstaller::appendData(&out, &res, res.size());
|
|
input.resourcePos.append(Range<qint64>::fromStartAndEnd(out.pos() - res.size(), out.pos())
|
|
.moved(-dataBlockStart));
|
|
|
|
// append given resource files
|
|
foreach (const QString &resource, input.binaryResources) {
|
|
QFile res(resource);
|
|
QInstaller::openForRead(&res);
|
|
QInstaller::appendData(&out, &res, res.size());
|
|
input.resourcePos.append(Range<qint64>::fromStartAndEnd(out.pos() - res.size(), out.pos())
|
|
.moved(-dataBlockStart));
|
|
}
|
|
|
|
// zero operations cause we are not the uninstaller
|
|
const qint64 operationsStart = out.pos();
|
|
QInstaller::appendInt64(&out, 0);
|
|
QInstaller::appendInt64(&out, 0);
|
|
input.operationsPos = Range<qint64>::fromStartAndEnd(operationsStart, out.pos())
|
|
.moved(-dataBlockStart);
|
|
|
|
// component index:
|
|
input.components.writeComponentData(&out, -dataBlockStart);
|
|
const qint64 compIndexStart = out.pos() - dataBlockStart;
|
|
input.components.writeIndex(&out, -dataBlockStart);
|
|
input.componentIndexSegment = Range<qint64>::fromStartAndEnd(compIndexStart, out.pos()
|
|
- dataBlockStart);
|
|
|
|
qDebug("Component index: [%llu:%llu]", input.componentIndexSegment.start(),
|
|
input.componentIndexSegment.end());
|
|
QInstaller::appendInt64Range(&out, input.componentIndexSegment);
|
|
foreach (const Range<qint64> &range, input.resourcePos)
|
|
QInstaller::appendInt64Range(&out, range);
|
|
QInstaller::appendInt64Range(&out, input.operationsPos);
|
|
QInstaller::appendInt64(&out, input.resourcePos.count());
|
|
|
|
//data block size, from end of .exe to end of file
|
|
QInstaller::appendInt64(&out, out.pos() + 3 * sizeof(qint64) -dataBlockStart);
|
|
QInstaller::appendInt64(&out, QInstaller::MagicInstallerMarker);
|
|
QInstaller::appendInt64(&out, QInstaller::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("Could not write installer to %s: %s", targetName.toUtf8().constData(),
|
|
out.errorString().toUtf8().constData());
|
|
QFile::remove(tempFile);
|
|
return EXIT_FAILURE;
|
|
}
|
|
out.setAutoRemove(false);
|
|
|
|
#ifndef Q_OS_WIN
|
|
chmod755(out.fileName());
|
|
#endif
|
|
QFile::remove(tempFile);
|
|
|
|
#ifdef Q_OS_MAC
|
|
bundleBackup.release();
|
|
|
|
if (createDMG) {
|
|
qDebug() << "creating a DMG disk image...";
|
|
// no error handling as this is not fatal
|
|
const QString mkdmgscript = QDir::temp().absoluteFilePath(QLatin1String("mkdmg.sh"));
|
|
QFile::copy(QLatin1String(":/resources/mkdmg.sh"), mkdmgscript);
|
|
chmod755(mkdmgscript);
|
|
|
|
QProcess p;
|
|
p.start(mkdmgscript, QStringList() << QFileInfo(input.outputPath).fileName() << bundle);
|
|
p.waitForFinished();
|
|
QFile::remove(mkdmgscript);
|
|
qDebug() << "done." << mkdmgscript;
|
|
}
|
|
#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, 0);
|
|
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;
|
|
}
|
|
|
|
class WorkingDirectoryChange
|
|
{
|
|
public:
|
|
explicit WorkingDirectoryChange(const QString &path)
|
|
: oldPath(QDir::currentPath())
|
|
{
|
|
QDir::setCurrent(path);
|
|
}
|
|
|
|
virtual ~WorkingDirectoryChange()
|
|
{
|
|
QDir::setCurrent(oldPath);
|
|
}
|
|
|
|
private:
|
|
const QString oldPath;
|
|
};
|
|
|
|
static QString createBinaryResourceFile(const QString &directory, const QString &binaryName)
|
|
{
|
|
QTemporaryFile projectFile(directory + QLatin1String("/rccprojectXXXXXX.qrc"));
|
|
if (!projectFile.open())
|
|
throw Error(QString::fromLatin1("Could not 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("Could not 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("Could not compile rcc project file."));
|
|
}
|
|
|
|
return binaryName;
|
|
}
|
|
|
|
static QStringList createBinaryResourceFiles(const QStringList &resources)
|
|
{
|
|
QStringList 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)
|
|
result.append(binaryName);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void printUsage()
|
|
{
|
|
QString suffix;
|
|
#ifdef Q_OS_WIN
|
|
suffix = QLatin1String(".exe");
|
|
#endif
|
|
const QString appName = QFileInfo(QCoreApplication::applicationFilePath()).fileName();
|
|
std::cout << "Usage: " << appName << " [options] target" << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Options:" << std::endl;
|
|
|
|
std::cout << " -t|--template file Use file as installer template binary" << std::endl;
|
|
std::cout << " If this parameter is not given, the template used" << std::endl;
|
|
std::cout << " defaults to installerbase." << std::endl;
|
|
|
|
QInstallerTools::printRepositoryGenOptions();
|
|
|
|
std::cout << " -c|--config file The file containing the installer configuration" << std::endl;
|
|
|
|
std::cout << " -n|--online-only Do not add any package into the installer" << std::endl;
|
|
std::cout << " (for online only installers)" << std::endl;
|
|
|
|
std::cout << " -f|--offline-only Forces the installer to act as an offline installer, " << std::endl;
|
|
std::cout << " i.e. never access online repositories" << std::endl;
|
|
|
|
std::cout << " -r|--resources r1,.,rn include the given resource files into the binary" << std::endl;
|
|
|
|
std::cout << " -v|--verbose Verbose output" << std::endl;
|
|
std::cout << " -rcc|--compile-resource Compiles the default resource and outputs the result into"
|
|
<< std::endl;
|
|
std::cout << " 'update.rcc' in the current path." << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Packages are to be found in the current working directory and get listed as "
|
|
"their names" << std::endl << std::endl;
|
|
std::cout << "Example (offline installer):" << std::endl;
|
|
char sep = QDir::separator().toLatin1();
|
|
std::cout << " " << appName << " --offline-only -c installer-config" << sep << "config.xml -p "
|
|
"packages-directory -t installerbase" << suffix << " SDKInstaller" << suffix << std::endl;
|
|
std::cout << "Creates an offline installer for the SDK, containing all dependencies." << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Example (online installer):" << std::endl;
|
|
std::cout << " " << appName << " -c installer-config" << sep << "config.xml -p packages-directory "
|
|
"-e org.qt-project.sdk.qt,org.qt-project.qtcreator -t installerbase" << suffix << " SDKInstaller"
|
|
<< suffix << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Creates an installer for the SDK without qt and qt creator." << std::endl;
|
|
std::cout << std::endl;
|
|
std::cout << "Example update.rcc:" << std::endl;
|
|
std::cout << " " << appName << " -c installer-config" << sep << "config.xml -p packages-directory "
|
|
"-rcc" << std::endl;
|
|
}
|
|
|
|
void 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() << QString::fromLatin1("Read dom element: <%1>%2</%1>.").arg(tagName, elementText);
|
|
|
|
QString newName = domElement.text().replace(QRegExp(QLatin1String("\\\\|/|\\.|:")),
|
|
QLatin1String("_"));
|
|
|
|
QString targetFile;
|
|
QFileInfo elementFileInfo;
|
|
if (tagName == QLatin1String("Icon") || tagName == QLatin1String("InstallerApplicationIcon")) {
|
|
#if defined(Q_OS_MAC)
|
|
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";
|
|
}
|
|
|
|
static int printErrorAndUsageAndExit(const QString &err)
|
|
{
|
|
std::cerr << qPrintable(err) << std::endl << std::endl;
|
|
printUsage();
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
QCoreApplication app(argc, argv);
|
|
|
|
QInstaller::init();
|
|
|
|
QString templateBinary = QLatin1String("installerbase");
|
|
QString suffix;
|
|
#ifdef Q_OS_WIN
|
|
suffix = QLatin1String(".exe");
|
|
templateBinary = templateBinary + suffix;
|
|
#endif
|
|
if (!QFileInfo(templateBinary).exists())
|
|
templateBinary = QString::fromLatin1("%1/%2").arg(qApp->applicationDirPath(), templateBinary);
|
|
|
|
QString target;
|
|
QString configFile;
|
|
QStringList packagesDirectories;
|
|
bool onlineOnly = false;
|
|
bool offlineOnly = false;
|
|
QStringList resources;
|
|
QStringList filteredPackages;
|
|
QInstallerTools::FilterType ftype = QInstallerTools::Exclude;
|
|
bool compileResource = false;
|
|
|
|
const QStringList args = app.arguments().mid(1);
|
|
for (QStringList::const_iterator it = args.begin(); it != args.end(); ++it) {
|
|
if (*it == QLatin1String("-h") || *it == QLatin1String("--help")) {
|
|
printUsage();
|
|
return 0;
|
|
} else if (*it == QLatin1String("-p") || *it == QLatin1String("--packages")) {
|
|
++it;
|
|
if (it == args.end()) {
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Packages parameter missing argument."));
|
|
}
|
|
if (!QFileInfo(*it).exists()) {
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Package directory not found at the "
|
|
"specified location."));
|
|
}
|
|
packagesDirectories.append(*it);
|
|
} else if (*it == QLatin1String("-e") || *it == QLatin1String("--exclude")) {
|
|
++it;
|
|
if (!filteredPackages.isEmpty())
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: --include and --exclude are mutually "
|
|
"exclusive. Use either one or the other."));
|
|
if (it == args.end() || it->startsWith(QLatin1String("-")))
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Package to exclude missing."));
|
|
filteredPackages = it->split(QLatin1Char(','));
|
|
} else if (*it == QLatin1String("-i") || *it == QLatin1String("--include")) {
|
|
++it;
|
|
if (!filteredPackages.isEmpty())
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: --include and --exclude are mutually "
|
|
"exclusive. Use either one or the other."));
|
|
if (it == args.end() || it->startsWith(QLatin1String("-")))
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Package to include missing."));
|
|
filteredPackages = it->split(QLatin1Char(','));
|
|
ftype = QInstallerTools::Include;
|
|
}
|
|
else if (*it == QLatin1String("-v") || *it == QLatin1String("--verbose")) {
|
|
QInstaller::setVerbose(true);
|
|
} else if (*it == QLatin1String("-n") || *it == QLatin1String("--online-only")) {
|
|
if (!filteredPackages.isEmpty()) {
|
|
return printErrorAndUsageAndExit(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."));
|
|
}
|
|
onlineOnly = true;
|
|
} else if (*it == QLatin1String("-f") || *it == QLatin1String("--offline-only")) {
|
|
offlineOnly = true;
|
|
} else if (*it == QLatin1String("-t") || *it == QLatin1String("--template")) {
|
|
++it;
|
|
if (it == args.end()) {
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Template parameter missing argument."));
|
|
}
|
|
templateBinary = *it;
|
|
#ifdef Q_OS_WIN
|
|
if (!templateBinary.endsWith(suffix))
|
|
templateBinary = templateBinary + suffix;
|
|
#endif
|
|
if (!QFileInfo(templateBinary).exists()) {
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Template not found at the specified "
|
|
"location."));
|
|
}
|
|
} else if (*it == QLatin1String("-c") || *it == QLatin1String("--config")) {
|
|
++it;
|
|
if (it == args.end())
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Config parameter missing argument."));
|
|
const QFileInfo fi(*it);
|
|
if (!fi.exists()) {
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Config file %1 not found at the "
|
|
"specified location.").arg(*it));
|
|
}
|
|
if (!fi.isFile()) {
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Configuration %1 is not a file.")
|
|
.arg(*it));
|
|
}
|
|
if (!fi.isReadable()) {
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Config file %1 is not readable.")
|
|
.arg(*it));
|
|
}
|
|
configFile = *it;
|
|
} else if (*it == QLatin1String("-r") || *it == QLatin1String("--resources")) {
|
|
++it;
|
|
if (it == args.end() || it->startsWith(QLatin1String("-")))
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Resource files to include are missing."));
|
|
resources = it->split(QLatin1Char(','));
|
|
} else if (*it == QLatin1String("--ignore-translations")
|
|
|| *it == QLatin1String("--ignore-invalid-packages")) {
|
|
continue;
|
|
} else if (*it == QLatin1String("-rcc") || *it == QLatin1String("--compile-resource")) {
|
|
compileResource = true;
|
|
} else {
|
|
if (it->startsWith(QLatin1String("-"))) {
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Unknown option \"%1\" used. Maybe you "
|
|
"are using an old syntax.").arg(*it));
|
|
} else if (target.isEmpty()) {
|
|
target = *it;
|
|
#ifdef Q_OS_WIN
|
|
if (!target.endsWith(suffix))
|
|
target = target + suffix;
|
|
#endif
|
|
} else {
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: You are using an old syntax please add the "
|
|
"component name with the include option")
|
|
.arg(*it));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (onlineOnly && offlineOnly) {
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("You cannot use --online-only and "
|
|
"--offline-only at the same time."));
|
|
}
|
|
|
|
if (onlineOnly) {
|
|
filteredPackages.append(QLatin1String("X_fake_filter_component_for_online_only_installer_X"));
|
|
ftype = QInstallerTools::Include;
|
|
}
|
|
|
|
if (target.isEmpty() && !compileResource)
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Target parameter missing."));
|
|
|
|
if (configFile.isEmpty())
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: No configuration file selected."));
|
|
|
|
if (packagesDirectories.isEmpty())
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Package directory parameter missing."));
|
|
|
|
qDebug() << "Parsed arguments, ok.";
|
|
|
|
int exitCode = EXIT_FAILURE;
|
|
QTemporaryDir tmp;
|
|
tmp.setAutoRemove(false);
|
|
const QString tmpMetaDir = tmp.path();
|
|
try {
|
|
const Settings settings = Settings::fromFileAndPrefix(configFile, QFileInfo(configFile).absolutePath());
|
|
QInstallerTools::PackageInfoVector packages = QInstallerTools::createListOfPackages(packagesDirectories,
|
|
&filteredPackages, ftype);
|
|
QInstallerTools::copyMetaData(tmpMetaDir, packagesDirectories.first(), packages, settings.applicationName(),
|
|
settings.applicationVersion());
|
|
|
|
copyConfigData(configFile, tmpMetaDir + QLatin1String("/installer-config"));
|
|
{
|
|
QSettings confInternal(tmpMetaDir + QLatin1String("/config/config-internal.ini")
|
|
, QSettings::IniFormat);
|
|
// assume offline installer if there are no repositories
|
|
offlineOnly |= settings.repositories().isEmpty();
|
|
confInternal.setValue(QLatin1String("offlineOnly"), offlineOnly);
|
|
}
|
|
|
|
#if defined(Q_OS_MAC)
|
|
// on mac, we enforce building a bundle
|
|
if (!target.endsWith(QLatin1String(".app")) && !target.endsWith(QLatin1String(".dmg")))
|
|
target += QLatin1String(".app");
|
|
#endif
|
|
if (!compileResource) {
|
|
Input input;
|
|
input.outputPath = target;
|
|
input.installerExePath = templateBinary;
|
|
input.binaryResourcePath = createBinaryResourceFile(tmpMetaDir, generateTemporaryFileName());
|
|
input.binaryResources = createBinaryResourceFiles(resources);
|
|
|
|
QInstallerTools::copyComponentData(packagesDirectories, tmpMetaDir, &packages);
|
|
|
|
// now put the packages into the components section of the binary
|
|
foreach (const QInstallerTools::PackageInfo &info, packages) {
|
|
Component comp;
|
|
comp.setName(info.name.toUtf8());
|
|
|
|
qDebug() << "Creating component info for" << info.name;
|
|
foreach (const QString &archive, info.copiedFiles) {
|
|
const QSharedPointer<Archive> arch(new Archive(archive));
|
|
qDebug() << QString::fromLatin1("Appending %1 (%2)").arg(archive,
|
|
humanReadableSize(arch->size()));
|
|
comp.appendArchive(arch);
|
|
}
|
|
input.components.insertComponent(comp);
|
|
}
|
|
|
|
qDebug() << "Creating the binary";
|
|
exitCode = assemble(input, settings);
|
|
|
|
// cleanup
|
|
qDebug() << "Cleaning up...";
|
|
QFile::remove(input.binaryResourcePath);
|
|
foreach (const QString &resource, input.binaryResources)
|
|
QFile::remove(resource);
|
|
} else {
|
|
createBinaryResourceFile(tmpMetaDir, QDir::currentPath() + QLatin1String("/update.rcc"));
|
|
exitCode = EXIT_SUCCESS;
|
|
}
|
|
} catch (const Error &e) {
|
|
std::cerr << "Caught exception: " << e.message() << std::endl;
|
|
} catch (...) {
|
|
std::cerr << "Unknown exception caught" << std::endl;
|
|
}
|
|
|
|
QInstaller::removeDirectory(tmpMetaDir, true);
|
|
return exitCode;
|
|
}
|