822 lines
33 KiB
C++
Raw Normal View History

2011-02-21 16:30:31 +01:00
/**************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
2011-02-21 16:30:31 +01:00
**
** This file is part of the Qt Installer Framework.
2011-02-21 16:30:31 +01:00
**
** $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.
2011-02-21 16:30:31 +01:00
**
** 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$
2011-02-21 16:30:31 +01:00
**
**************************************************************************/
#include "common/repositorygen.h"
#include <qtpatch.h>
#include <binarycontent.h>
#include <binaryformat.h>
#include <errors.h>
#include <fileio.h>
#include <fileutils.h>
2011-06-21 13:37:14 +02:00
#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>
2011-02-21 16:30:31 +01:00
#include <iostream>
2011-02-21 16:30:31 +01:00
using namespace QInstaller;
struct Input {
QString outputPath;
QString installerExePath;
QInstallerTools::PackageInfoVector packages;
QInstaller::ResourceCollectionManager manager;
2011-02-21 16:30:31 +01:00
};
class BundleBackup
{
public:
explicit BundleBackup(const QString &bundle = QString())
: bundle(bundle)
{
if (!bundle.isEmpty() && QFileInfo(bundle).exists()) {
backup = generateTemporaryFileName(bundle);
QFile::rename(bundle, backup);
}
}
2011-02-21 16:30:31 +01:00
~BundleBackup()
{
if (!backup.isEmpty()) {
removeDirectory(bundle);
QFile::rename(backup, bundle);
}
}
void release() const
{
2011-02-21 16:30:31 +01:00
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, const QString &signingIdentity)
2011-02-21 16:30:31 +01:00
{
#ifdef Q_OS_OSX
if (QInstaller::isInBundle(input.installerExePath)) {
2011-02-21 16:30:31 +01:00
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());
2011-02-21 16:30:31 +01:00
}
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/");
2011-02-21 16:30:31 +01:00
QInstaller::mkpath(fi.filePath() + QLatin1String("/Contents/MacOS"));
QInstaller::mkpath(contentsResourcesPath);
2011-02-21 16:30:31 +01:00
2011-03-06 20:03:56 +01:00
{
QFile pkgInfo(fi.filePath() + QLatin1String("/Contents/PkgInfo"));
pkgInfo.open(QIODevice::WriteOnly);
QTextStream pkgInfoStream(&pkgInfo);
pkgInfoStream << QLatin1String("APPL????") << endl;
}
2011-02-21 16:30:31 +01:00
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"));
}
2011-02-21 16:30:31 +01:00
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_STR)) << ("</string>")
<< endl;
#undef QUOTE
#undef QUOTE_
2011-02-21 16:30:31 +01:00
plistStream << QLatin1String(" <key>CFBundleSignature</key>") << endl;
plistStream << QLatin1String(" <string> ???? </string>") << endl;
plistStream << QLatin1String(" <key>CFBundleExecutable</key>") << endl;
plistStream << QLatin1String(" <string>") << fi.completeBaseName() << QLatin1String("</string>")
2011-02-21 16:30:31 +01:00
<< 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>")
2011-02-21 16:30:31 +01:00
<< endl;
plistStream << QLatin1String(" <key>NSPrincipalClass</key>") << endl;
plistStream << QLatin1String(" <string>NSApplication</string>") << endl;
2011-02-21 16:30:31 +01:00
plistStream << QLatin1String("</dict>") << endl;
plistStream << QLatin1String("</plist>") << endl;
input.outputPath = QString::fromLatin1("%1/Contents/MacOS/%2").arg(input.outputPath)
.arg(fi.completeBaseName());
2011-02-21 16:30:31 +01:00
}
#elif defined(Q_OS_LINUX)
Q_UNUSED(settings)
2011-02-21 16:30:31 +01:00
#endif
QTemporaryFile file(input.outputPath);
if (!file.open()) {
throw Error(QString::fromLatin1("Cannot copy %1 to %2: %3").arg(input.installerExePath,
2011-02-21 16:30:31 +01:00
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()));
2011-02-21 16:30:31 +01:00
}
QtPatch::patchBinaryFile(tempFile, QByteArray("MY_InstallerCreateDateTime_MY"),
QDateTime::currentDateTime().toString(QLatin1String("yyyy-MM-dd - HH:mm:ss")).toLatin1());
2011-02-21 16:30:31 +01:00
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())) {
2011-02-21 16:30:31 +01:00
// no error handling as this is not fatal
setApplicationIcon(tempFile, settings.installerApplicationIcon());
2011-02-21 16:30:31 +01:00
}
#elif defined(Q_OS_OSX)
2011-02-21 16:30:31 +01:00
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);
2011-02-21 16:30:31 +01:00
QProcess p;
p.start(copyscript, QStringList() << bundle);
p.waitForFinished(-1);
2011-02-21 16:30:31 +01:00
QFile::rename(input.outputPath, tempFile);
QFile::remove(copyscript);
}
#endif
QTemporaryFile out;
QString targetName = input.outputPath;
#ifdef Q_OS_OSX
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;
}
}
2011-02-21 16:30:31 +01:00
try {
QInstaller::openForWrite(&out);
QFile exe(input.installerExePath);
#ifdef Q_OS_OSX
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
2011-02-21 16:30:31 +01:00
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);
}
2011-02-21 16:30:31 +01:00
const QList<QInstaller::OperationBlob> operations;
BinaryContent::writeBinaryContent(&out, operations, input.manager,
BinaryContent::MagicInstallerMarker, BinaryContent::MagicCookie);
2011-02-21 16:30:31 +01:00
} catch (const Error &e) {
qCritical("Error occurred while assembling the installer: %s", qPrintable(e.message()));
QFile::remove(tempFile);
return EXIT_FAILURE;
2011-02-21 16:30:31 +01:00
}
if (!out.rename(targetName)) {
qCritical("Cannot write installer to %s: %s", targetName.toUtf8().constData(),
out.errorString().toUtf8().constData());
2011-02-21 16:30:31 +01:00
QFile::remove(tempFile);
return EXIT_FAILURE;
2011-02-21 16:30:31 +01:00
}
out.setAutoRemove(false);
2011-02-21 16:30:31 +01:00
#ifndef Q_OS_WIN
chmod755(out.fileName());
2011-02-21 16:30:31 +01:00
#endif
QFile::remove(tempFile);
#ifdef Q_OS_OSX
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.";
}
2011-02-21 16:30:31 +01:00
bundleBackup.release();
if (createDMG) {
qDebug() << "creating a DMG disk image...";
Fixed (and greatly simplified) creating .dmg files The mkdmg.sh script which is used to create .dmg files made some effort to calculate the size of the file system contained in the .dmg file. However, it neglected to consider that a .dmg file is not always (never?) totally empty. A sample session on OS X 10.10.5 shows: $ hdiutil create /tmp/foo.dmg -megabytes 33 -ov -volname foo -type UDIF -fs HFS+ ... <a lot more dots here> created: /tmp/foo.dmg $ hdid /tmp/foo.dmg /dev/disk3 GUID_partition_scheme /dev/disk3s1 Apple_HFS /Volumes/foo $ df /Volumes/foo Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on /dev/disk3s1 67504 1584 65920 3% 196 8240 2% /Volumes/foo About 792kB are apparently used for bookkeeping (in ".Trashes" and ".fseventsd") directories. Since mkdmg.sh failed to account for these 792kB, the .dmg file it creates was occasionally too small, causing an error message to be printed by the 'cp' command in the mkdmg.sh script (but that error message was never printed anywhere, at least I never noticed when executing binarycreator - always only when running mkdmg.sh directly). Instead of fixing the allocated size, let's just make use of the '-srcfolder' argument to 'hdiutil create' which does all of this automatically. It requires no copying around or calculating sizes, which not only makes the script much simpler, it also avoids the above-mentioned issue but also makes the script run faster: about 30% faster in my experiments. With this, the mkdmg.sh script is so small (just one hdiutil and one rm call) that we may just as well drop it completely and perform those operations straight from the C++ code. A final nice side-effect is that '-srcfolder' can be specified multiple times: this makes it very easy to copy multiple directory trees in the resulting .dmg file. This in turn is useful to create nicely styled .dmg files with custom background image and icon arrangements. Change-Id: I54d63a00e56d1ee9fa61c2690ca42d512fda37b1 Reviewed-by: Karsten Heimrich <karsten.heimrich@theqtcompany.com>
2016-04-04 17:54:20 +02:00
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
2011-02-21 16:30:31 +01:00
QProcess p;
Fixed (and greatly simplified) creating .dmg files The mkdmg.sh script which is used to create .dmg files made some effort to calculate the size of the file system contained in the .dmg file. However, it neglected to consider that a .dmg file is not always (never?) totally empty. A sample session on OS X 10.10.5 shows: $ hdiutil create /tmp/foo.dmg -megabytes 33 -ov -volname foo -type UDIF -fs HFS+ ... <a lot more dots here> created: /tmp/foo.dmg $ hdid /tmp/foo.dmg /dev/disk3 GUID_partition_scheme /dev/disk3s1 Apple_HFS /Volumes/foo $ df /Volumes/foo Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on /dev/disk3s1 67504 1584 65920 3% 196 8240 2% /Volumes/foo About 792kB are apparently used for bookkeeping (in ".Trashes" and ".fseventsd") directories. Since mkdmg.sh failed to account for these 792kB, the .dmg file it creates was occasionally too small, causing an error message to be printed by the 'cp' command in the mkdmg.sh script (but that error message was never printed anywhere, at least I never noticed when executing binarycreator - always only when running mkdmg.sh directly). Instead of fixing the allocated size, let's just make use of the '-srcfolder' argument to 'hdiutil create' which does all of this automatically. It requires no copying around or calculating sizes, which not only makes the script much simpler, it also avoids the above-mentioned issue but also makes the script run faster: about 30% faster in my experiments. With this, the mkdmg.sh script is so small (just one hdiutil and one rm call) that we may just as well drop it completely and perform those operations straight from the C++ code. A final nice side-effect is that '-srcfolder' can be specified multiple times: this makes it very easy to copy multiple directory trees in the resulting .dmg file. This in turn is useful to create nicely styled .dmg files with custom background image and icon arrangements. Change-Id: I54d63a00e56d1ee9fa61c2690ca42d512fda37b1 Reviewed-by: Karsten Heimrich <karsten.heimrich@theqtcompany.com>
2016-04-04 17:54:20 +02:00
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);
Fixed (and greatly simplified) creating .dmg files The mkdmg.sh script which is used to create .dmg files made some effort to calculate the size of the file system contained in the .dmg file. However, it neglected to consider that a .dmg file is not always (never?) totally empty. A sample session on OS X 10.10.5 shows: $ hdiutil create /tmp/foo.dmg -megabytes 33 -ov -volname foo -type UDIF -fs HFS+ ... <a lot more dots here> created: /tmp/foo.dmg $ hdid /tmp/foo.dmg /dev/disk3 GUID_partition_scheme /dev/disk3s1 Apple_HFS /Volumes/foo $ df /Volumes/foo Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on /dev/disk3s1 67504 1584 65920 3% 196 8240 2% /Volumes/foo About 792kB are apparently used for bookkeeping (in ".Trashes" and ".fseventsd") directories. Since mkdmg.sh failed to account for these 792kB, the .dmg file it creates was occasionally too small, causing an error message to be printed by the 'cp' command in the mkdmg.sh script (but that error message was never printed anywhere, at least I never noticed when executing binarycreator - always only when running mkdmg.sh directly). Instead of fixing the allocated size, let's just make use of the '-srcfolder' argument to 'hdiutil create' which does all of this automatically. It requires no copying around or calculating sizes, which not only makes the script much simpler, it also avoids the above-mentioned issue but also makes the script run faster: about 30% faster in my experiments. With this, the mkdmg.sh script is so small (just one hdiutil and one rm call) that we may just as well drop it completely and perform those operations straight from the C++ code. A final nice side-effect is that '-srcfolder' can be specified multiple times: this makes it very easy to copy multiple directory trees in the resulting .dmg file. This in turn is useful to create nicely styled .dmg files with custom background image and icon arrangements. Change-Id: I54d63a00e56d1ee9fa61c2690ca42d512fda37b1 Reviewed-by: Karsten Heimrich <karsten.heimrich@theqtcompany.com>
2016-04-04 17:54:20 +02:00
qDebug() << "removing" << bundle;
QDir(bundle).removeRecursively();
qDebug() << "done.";
2011-02-21 16:30:31 +01:00
}
#else
Q_UNUSED(signingIdentity)
2011-02-21 16:30:31 +01:00
#endif
return EXIT_SUCCESS;
2011-02-21 16:30:31 +01:00
}
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...
2011-02-21 16:30:31 +01:00
const int result = runRcc(argc, argv.data());
2011-06-21 13:37:14 +02:00
foreach (char *arg, argv)
delete [] arg;
2011-02-21 16:30:31 +01:00
return result;
}
class WorkingDirectoryChange
{
public:
explicit WorkingDirectoryChange(const QString &path)
2011-02-21 16:30:31 +01:00
: oldPath(QDir::currentPath())
{
QDir::setCurrent(path);
}
virtual ~WorkingDirectoryChange()
{
QDir::setCurrent(oldPath);
}
private:
const QString oldPath;
};
static QSharedPointer<QInstaller::Resource> createDefaultResourceFile(const QString &directory,
const QString &binaryName)
2011-02-21 16:30:31 +01:00
{
QTemporaryFile projectFile(directory + QLatin1String("/rccprojectXXXXXX.qrc"));
2011-02-21 16:30:31 +01:00
if (!projectFile.open())
throw Error(QString::fromLatin1("Cannot create temporary file for generated rcc project file"));
2011-02-21 16:30:31 +01:00
projectFile.close();
const WorkingDirectoryChange wd(directory);
2011-06-21 13:37:14 +02:00
const QString projectFileName = QFileInfo(projectFile.fileName()).absoluteFilePath();
2011-02-21 16:30:31 +01:00
// 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."));
}
2011-02-21 16:30:31 +01:00
// 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."));
}
2011-02-21 16:30:31 +01:00
return QSharedPointer<QInstaller::Resource>(new QInstaller::Resource(binaryName, binaryName
.toUtf8()));
2011-02-21 16:30:31 +01:00
}
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;
}
2011-02-21 16:30:31 +01:00
static void printUsage()
{
QString suffix;
#ifdef Q_OS_WIN
suffix = QLatin1String(".exe");
#endif
2011-02-21 16:30:31 +01:00
const QString appName = QFileInfo(QCoreApplication::applicationFilePath()).fileName();
std::cout << "Usage: " << appName << " [options] target" << std::endl;
2011-02-21 16:30:31 +01:00
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;
#ifdef Q_OS_OSX
std::cout << " -s|--sign identity Sign generated app bundle using the given code " << std::endl;
std::cout << " signing identity" << std::endl;
#endif
std::cout << std::endl;
2011-02-21 16:30:31 +01:00
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;
2011-02-21 16:30:31 +01:00
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;
2011-02-21 16:30:31 +01:00
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;
2011-02-21 16:30:31 +01:00
}
void copyConfigData(const QString &configFile, const QString &targetDir)
2011-02-21 16:30:31 +01:00
{
qDebug() << "Begin to copy configuration file and data.";
2011-02-21 16:30:31 +01:00
const QString sourceConfigFile = QFileInfo(configFile).absoluteFilePath();
const QString targetConfigFile = targetDir + QLatin1String("/config.xml");
QInstallerTools::copyWithException(sourceConfigFile, targetConfigFile, QLatin1String("configuration"));
2011-02-21 16:30:31 +01:00
QFile configXml(targetConfigFile);
QInstaller::openForRead(&configXml);
2011-02-21 16:30:31 +01:00
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())
2011-02-21 16:30:31 +01:00
continue;
const QString tagName = domElement.tagName();
const QString elementText = domElement.text();
qDebug().noquote() << 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_OSX)
const QString suffix = QLatin1String(".icns");
#elif defined(Q_OS_WIN)
const QString suffix = QLatin1String(".ico");
2011-02-21 16:30:31 +01:00
#else
const QString suffix = QLatin1String(".png");
2011-02-21 16:30:31 +01:00
#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;
2011-02-21 16:30:31 +01:00
domElement.replaceChild(dom.createTextNode(newName), domElement.firstChild());
QInstallerTools::copyWithException(elementFileInfo.absoluteFilePath(), targetFile, tagName);
}
2011-02-21 16:30:31 +01:00
QInstaller::openForWrite(&configXml);
QTextStream stream(&configXml);
dom.save(stream, 4);
2011-02-21 16:30:31 +01:00
qDebug() << "done.\n";
2011-02-21 16:30:31 +01:00
}
static int printErrorAndUsageAndExit(const QString &err)
{
std::cerr << qPrintable(err) << std::endl << std::endl;
printUsage();
2011-06-21 13:37:14 +02:00
return EXIT_FAILURE;
2011-02-21 16:30:31 +01:00
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
2011-06-21 13:37:14 +02:00
QInstaller::init();
2011-02-21 16:30:31 +01:00
2011-06-21 13:37:14 +02:00
QString templateBinary = QLatin1String("installerbase");
QString suffix;
#ifdef Q_OS_WIN
suffix = QLatin1String(".exe");
templateBinary = templateBinary + suffix;
2011-02-21 16:30:31 +01:00
#endif
if (!QFileInfo(templateBinary).exists())
templateBinary = QString::fromLatin1("%1/%2").arg(qApp->applicationDirPath(), templateBinary);
2011-06-21 13:37:14 +02:00
2011-02-21 16:30:31 +01:00
QString target;
QString configFile;
QStringList packagesDirectories;
bool onlineOnly = false;
2011-02-21 16:30:31 +01:00
bool offlineOnly = false;
QStringList resources;
QStringList filteredPackages;
QInstallerTools::FilterType ftype = QInstallerTools::Exclude;
bool compileResource = false;
QString signingIdentity;
2011-06-21 13:37:14 +02:00
const QStringList args = app.arguments().mid(1);
2011-02-21 16:30:31 +01:00
for (QStringList::const_iterator it = args.begin(); it != args.end(); ++it) {
if (*it == QLatin1String("-h") || *it == QLatin1String("--help")) {
2011-02-21 16:30:31 +01:00
printUsage();
return 0;
} else if (*it == QLatin1String("-p") || *it == QLatin1String("--packages")) {
2011-02-21 16:30:31 +01:00
++it;
if (it == args.end()) {
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Packages parameter missing argument."));
2011-02-21 16:30:31 +01:00
}
if (!QFileInfo(*it).exists()) {
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Package directory not found at the "
2011-06-21 13:37:14 +02:00
"specified location."));
2011-02-21 16:30:31 +01:00
}
packagesDirectories.append(*it);
2011-02-21 16:30:31 +01:00
} 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."));
2011-02-21 16:30:31 +01:00
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")) {
2011-06-21 13:37:14 +02:00
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")) {
2011-02-21 16:30:31 +01:00
offlineOnly = true;
} else if (*it == QLatin1String("-t") || *it == QLatin1String("--template")) {
2011-02-21 16:30:31 +01:00
++it;
if (it == args.end()) {
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Template parameter missing argument."));
2011-02-21 16:30:31 +01:00
}
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 "
2011-06-21 13:37:14 +02:00
"location."));
2011-02-21 16:30:31 +01:00
}
} else if (*it == QLatin1String("-c") || *it == QLatin1String("--config")) {
++it;
if (it == args.end())
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Config parameter missing argument."));
2011-02-21 16:30:31 +01:00
const QFileInfo fi(*it);
if (!fi.exists()) {
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Config file %1 not found at the "
2011-06-21 13:37:14 +02:00
"specified location.").arg(*it));
2011-02-21 16:30:31 +01:00
}
if (!fi.isFile()) {
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Configuration %1 is not a file.")
2011-06-21 13:37:14 +02:00
.arg(*it));
2011-02-21 16:30:31 +01:00
}
if (!fi.isReadable()) {
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Config file %1 is not readable.")
2011-06-21 13:37:14 +02:00
.arg(*it));
2011-02-21 16:30:31 +01:00
}
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;
#ifdef Q_OS_OSX
} else if (*it == QLatin1String("-s") || *it == QLatin1String("--sign")) {
++it;
if (it == args.end() || it->startsWith(QLatin1String("-")))
return printErrorAndUsageAndExit(QString::fromLatin1("Error: No code signing identity specified."));
signingIdentity = *it;
#endif
2011-02-21 16:30:31 +01:00
} 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()) {
2011-02-21 16:30:31 +01:00
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."));
2011-02-21 16:30:31 +01:00
if (configFile.isEmpty())
return printErrorAndUsageAndExit(QString::fromLatin1("Error: No configuration file selected."));
2011-02-21 16:30:31 +01:00
if (packagesDirectories.isEmpty())
return printErrorAndUsageAndExit(QString::fromLatin1("Error: Package directory parameter missing."));
qDebug() << "Parsed arguments, ok.";
2011-02-21 16:30:31 +01:00
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();
2011-02-21 16:30:31 +01:00
try {
const Settings settings = Settings::fromFileAndPrefix(configFile, QFileInfo(configFile)
.absolutePath());
// Note: the order here is important
// 1; create the list of available packages
QInstallerTools::PackageInfoVector packages =
QInstallerTools::createListOfPackages(packagesDirectories, &filteredPackages, ftype);
// 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
QInstallerTools::copyComponentData(packagesDirectories, tmpRepoDir, &packages);
// 3; copy the meta data of the available packages, generate Updates.xml
QInstallerTools::copyMetaData(tmpMetaDir, tmpRepoDir, packages, settings
.applicationName(), settings.version());
// 4; copy the configuration file and and icons etc.
copyConfigData(configFile, tmpMetaDir + QLatin1String("/installer-config"));
2011-02-21 16:30:31 +01:00
{
QSettings confInternal(tmpMetaDir + QLatin1String("/config/config-internal.ini")
, QSettings::IniFormat);
// assume offline installer if there are no repositories and no
//--online-only not set
offlineOnly = offlineOnly | settings.repositories().isEmpty();
if (onlineOnly)
offlineOnly = !onlineOnly;
2011-02-21 16:30:31 +01:00
confInternal.setValue(QLatin1String("offlineOnly"), offlineOnly);
}
#ifdef Q_OS_OSX
2011-02-21 16:30:31 +01:00
// on mac, we enforce building a bundle
if (!target.endsWith(QLatin1String(".app")) && !target.endsWith(QLatin1String(".dmg")))
2011-06-21 13:37:14 +02:00
target += QLatin1String(".app");
2011-02-21 16:30:31 +01:00
#endif
if (!compileResource) {
// 5; put the copied resources into a resource file
QInstaller::ResourceCollection metaCollection("QResources");
metaCollection.appendResource(createDefaultResourceFile(tmpMetaDir,
generateTemporaryFileName()));
metaCollection.appendResources(createBinaryResourceFiles(resources));
input.manager.insertCollection(metaCollection);
2011-02-21 16:30:31 +01:00
input.packages = packages;
input.outputPath = target;
input.installerExePath = templateBinary;
qDebug() << "Creating the binary";
exitCode = assemble(input, settings, signingIdentity);
} else {
createDefaultResourceFile(tmpMetaDir, QDir::currentPath() + QLatin1String("/update.rcc"));
exitCode = EXIT_SUCCESS;
}
2011-06-21 13:37:14 +02:00
} catch (const Error &e) {
QFile::remove(input.outputPath);
std::cerr << "Caught exception: " << e.message() << std::endl;
2011-06-21 13:37:14 +02:00
} catch (...) {
QFile::remove(input.outputPath);
2011-02-21 16:30:31 +01:00
std::cerr << "Unknown exception caught" << std::endl;
}
qDebug() << "Cleaning up...";
const QInstaller::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;
2011-02-21 16:30:31 +01:00
}