mirror of
https://github.com/QuasarApp/installer-framework.git
synced 2025-04-29 23:14:34 +00:00
The signature change is kind of a cosmetic one, but I think it is more obvious that passing a reference into a function might modify the parameter. Change-Id: I424c06ed7a449204bd0b243ff07c2d36669b7381 Reviewed-by: Tim Jenssen <tim.jenssen@digia.com>
758 lines
30 KiB
C++
758 lines
30 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 <fileutils.h>
|
|
#include <init.h>
|
|
#include <repository.h>
|
|
#include <settings.h>
|
|
#include <utils.h>
|
|
|
|
#include <kdsavefile.h>
|
|
|
|
#include <QtCore/QDirIterator>
|
|
#include <QtCore/QProcess>
|
|
#include <QtCore/QSettings>
|
|
#include <QtCore/QTemporaryFile>
|
|
|
|
#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);
|
|
QInstaller::mkpath(fi.filePath() + QLatin1String("/Contents/MacOS"));
|
|
QInstaller::mkpath(fi.filePath() + QLatin1String("/Contents/Resources"));
|
|
|
|
{
|
|
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, fi.filePath() + QLatin1String("/Contents/Resources/") + iconTargetFile);
|
|
|
|
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/QMake.</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
|
|
|
|
#ifdef Q_OS_MAC
|
|
QDir resourcePath(QFileInfo(input.outputPath).dir());
|
|
resourcePath.cdUp();
|
|
resourcePath.cd(QLatin1String("Resources"));
|
|
KDSaveFile out(resourcePath.filePath(QLatin1String("installer.dat")));
|
|
#else
|
|
KDSaveFile out(input.outputPath);
|
|
#endif
|
|
try {
|
|
#ifdef Q_OS_MAC
|
|
openForWrite(&out, out.fileName());
|
|
|
|
QFile exe(input.installerExePath);
|
|
if (!exe.copy(input.outputPath)) {
|
|
throw Error(QString::fromLatin1("Could not copy %1 to %2: %3").arg(exe.fileName(), input.outputPath,
|
|
exe.errorString()));
|
|
}
|
|
#else
|
|
openForWrite(&out, input.outputPath);
|
|
|
|
QFile exe(input.installerExePath);
|
|
openForRead(&exe, exe.fileName());
|
|
appendFileData(&out, &exe);
|
|
#endif
|
|
|
|
const qint64 dataBlockStart = out.pos();
|
|
qDebug() << "Data block starts at" << dataBlockStart;
|
|
|
|
// append our self created resource file
|
|
QFile res(input.binaryResourcePath);
|
|
openForRead(&res, res.fileName());
|
|
appendFileData(&out, &res);
|
|
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);
|
|
openForRead(&res, res.fileName());
|
|
appendFileData(&out, &res);
|
|
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();
|
|
appendInt64(&out, 0);
|
|
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());
|
|
appendInt64Range(&out, input.componentIndexSegment);
|
|
foreach (const Range<qint64> &range, input.resourcePos)
|
|
appendInt64Range(&out, range);
|
|
appendInt64Range(&out, input.operationsPos);
|
|
appendInt64(&out, input.resourcePos.count());
|
|
|
|
//data block size, from end of .exe to end of file
|
|
appendInt64(&out, out.pos() + 3 * sizeof(qint64) - dataBlockStart);
|
|
appendInt64(&out, QInstaller::MagicInstallerMarker);
|
|
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.commit(KDSaveFile::OverwriteExistingFile)) {
|
|
qCritical("Could not write installer to %s: %s", qPrintable(out.fileName()),
|
|
qPrintable(out.errorString()));
|
|
QFile::remove(tempFile);
|
|
return EXIT_FAILURE;
|
|
}
|
|
#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(out.fileName()).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)
|
|
{
|
|
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 binaryName = generateTemporaryFileName();
|
|
const QString projectFileName = QFileInfo(projectFile.fileName()).absoluteFilePath();
|
|
|
|
// 1. create the .qrc file
|
|
runRcc(QStringList() << QLatin1String("rcc") << QLatin1String("-project")
|
|
<< QLatin1String("-o") << projectFileName);
|
|
|
|
// 2. create the binary resource file from the .qrc file
|
|
runRcc(QStringList() << QLatin1String("rcc") << QLatin1String("-binary")
|
|
<< QLatin1String("-o") << binaryName << projectFileName);
|
|
|
|
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 Don't 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 << 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 com.nokia.sdk.qt,com.nokia.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;
|
|
}
|
|
|
|
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, configXml.fileName());
|
|
|
|
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, configXml.fileName());
|
|
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;
|
|
QString packagesDirectory = QDir::currentPath();
|
|
bool onlineOnly = false;
|
|
bool offlineOnly = false;
|
|
QStringList resources;
|
|
QStringList filteredPackages;
|
|
QInstallerTools::FilterType ftype = QInstallerTools::Exclude;
|
|
|
|
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."));
|
|
}
|
|
packagesDirectory = *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->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("XXXXXXXXXXXXXXXXX_online_XXXXXXXXXXXXXXXXX"));
|
|
ftype = QInstallerTools::Include;
|
|
}
|
|
|
|
if (target.isEmpty())
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Target parameter missing."));
|
|
|
|
if (configFile.isEmpty())
|
|
return printErrorAndUsageAndExit(QString::fromLatin1("Error: No configuration file selected."));
|
|
|
|
qDebug() << "Parsed arguments, ok.";
|
|
|
|
int exitCode = EXIT_FAILURE;
|
|
const QString tmpMetaDir = createTemporaryDirectory();
|
|
try {
|
|
const Settings settings = Settings::fromFileAndPrefix(configFile, QFileInfo(configFile).absolutePath());
|
|
QInstallerTools::PackageInfoVector packages = createListOfPackages(packagesDirectory,
|
|
filteredPackages, ftype);
|
|
copyMetaData(tmpMetaDir, packagesDirectory, 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
|
|
{
|
|
Input input;
|
|
input.outputPath = target;
|
|
input.installerExePath = templateBinary;
|
|
input.binaryResourcePath = createBinaryResourceFile(tmpMetaDir);
|
|
input.binaryResources = createBinaryResourceFiles(resources);
|
|
|
|
QInstallerTools::copyComponentData(packagesDirectory, 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.copiedArchives) {
|
|
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);
|
|
}
|
|
} catch (const Error &e) {
|
|
std::cerr << "caught exception: " << e.message() << std::endl;
|
|
} catch (...) {
|
|
std::cerr << "Unknown exception caught" << std::endl;
|
|
}
|
|
removeDirectory(tmpMetaDir, true);
|
|
return exitCode;
|
|
}
|