diff options
author | kh1 <karsten.heimrich@nokia.com> | 2012-03-15 14:53:47 +0100 |
---|---|---|
committer | Karsten Heimrich <karsten.heimrich@nokia.com> | 2012-03-19 16:14:04 +0100 |
commit | be3b47d0d504a3409ce66bd77bb8c0acff87c4f5 (patch) | |
tree | 09dfb02d484a4f395991972b828da71400fb761a /tools | |
parent | 9fd62353cf7f973d78cd2093328ac15b5c4980b6 (diff) |
Reorganize the tree, have better ifw.pri. Shadow build support.
Change-Id: I01fb12537f863ed0744979973c7e4153889cc5cb
Reviewed-by: Tim Jenssen <tim.jenssen@nokia.com>
Diffstat (limited to 'tools')
21 files changed, 3420 insertions, 30 deletions
diff --git a/tools/archivegen/archive.cpp b/tools/archivegen/archive.cpp new file mode 100644 index 000000000..2d1257ae7 --- /dev/null +++ b/tools/archivegen/archive.cpp @@ -0,0 +1,75 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "common/repositorygen.h" + +#include <errors.h> +#include <init.h> +#include <lib7z_facade.h> +#include <utils.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QFileInfo> +#include <QtCore/QStringList> + +#include <iostream> + +using namespace Lib7z; +using namespace QInstaller; + +static void printUsage() +{ + std::cout << "Usage: " << QFileInfo(QCoreApplication::applicationFilePath()).fileName() + << " directory.7z directories" << std::endl; +} + +int main(int argc, char *argv[]) +{ + try { + QCoreApplication app(argc, argv); + + if (app.arguments().count() < 3) { + printUsage(); + return EXIT_FAILURE; + } + + QInstaller::init(); + QInstaller::setVerbose(true); + const QStringList sourceDirectories = app.arguments().mid(2); + QInstallerTools::compressDirectory(sourceDirectories, app.arguments().at(1)); + return EXIT_SUCCESS; + } catch (const Lib7z::SevenZipException &e) { + std::cerr << e.message() << std::endl; + } catch (const QInstaller::Error &e) { + std::cerr << e.message() << std::endl; + } + return EXIT_FAILURE; +} diff --git a/tools/archivegen/archivegen.pro b/tools/archivegen/archivegen.pro new file mode 100644 index 000000000..9d778ee98 --- /dev/null +++ b/tools/archivegen/archivegen.pro @@ -0,0 +1,17 @@ +TEMPLATE = app +TARGET = archivegen +DEPENDPATH += . .. ../common +INCLUDEPATH += . .. ../common + +include(../../installerfw.pri) + +QT -= gui +LIBS += -linstaller + +CONFIG += console +CONFIG -= app_bundle +DESTDIR = $$IFW_APP_PATH + +SOURCES += archive.cpp \ + repositorygen.cpp +HEADERS += repositorygen.h diff --git a/tools/binarycreator/binarycreator.cpp b/tools/binarycreator/binarycreator.cpp new file mode 100644 index 000000000..c749fb276 --- /dev/null +++ b/tools/binarycreator/binarycreator.cpp @@ -0,0 +1,711 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "common/repositorygen.h" + +#include <binaryformat.h> +#include <errors.h> +#include <fileutils.h> +#include <init.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; +}; + +static void chmod755(const QString &absolutFilePath) +{ + QFile::setPermissions(absolutFilePath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner + | QFile::ReadGroup | QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther); +} + +static int assemble(Input input, const QString &configdir) +{ + const QString configfile = QFileInfo(configdir, QLatin1String("config.xml")).absoluteFilePath(); + const QInstaller::Settings &settings = QInstaller::Settings::fromFileAndPrefix(configfile, configdir); + +#ifdef Q_OS_LINUX +Q_UNUSED(settings) +#endif + +#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; + } + + const QString 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; + plistStream << QLatin1String(" <string>Created by Qt/QMake</string>") << endl; + 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()); + } +#endif + + QTemporaryFile file(input.outputPath); + if (!file.open()) { + throw Error(QObject::tr("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(QObject::tr("Could not copy %1 to %2: %3").arg(instExe.fileName(), tempFile, + instExe.errorString())); + } + 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.icon())) { + // no error handling as this is not fatal + 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 + + KDSaveFile out(input.outputPath); + try { + openForWrite(&out, input.outputPath); + + QFile exe(input.installerExePath); + openForRead(&exe, exe.fileName()); + appendFileData(&out, &exe); + + 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 1; + } + + if (!out.commit(KDSaveFile::OverwriteExistingFile)) { + qCritical("Could not write installer to %s: %s", qPrintable(out.fileName()), + qPrintable(out.errorString())); + QFile::remove(tempFile); + return 1; + } +#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 0; +} + +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])); + + 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(QObject::tr("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() +{ + 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 << " -n|--nodeps Don't add dependencies of package1...n into the " << std::endl; + std::cout << " installer (for online installers)" << std::endl; + + std::cout << " --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 << std::endl; + std::cout << " -v|--verbose Verbose output" << 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; + std::cout << " " << appName << " --offline-only -c installer-config -p packages-directory -t " + "installerbase SDKInstaller.exe" << 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 -p packages-directory -e com.nokia.sdk.qt," + "com.nokia.qtcreator -t installerbase SDKInstaller.exe" << 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; +} + +static QString createMetaDataDirectory(const QInstallerTools::PackageInfoVector &packages, + const QString &packagesDir, const QString &configdir) +{ + const QString configfile = QFileInfo(configdir, QLatin1String("config.xml")).absoluteFilePath(); + const QInstaller::Settings &settings = QInstaller::Settings::fromFileAndPrefix(configfile, QString()); + + const QString metapath = createTemporaryDirectory(); + generateMetaDataDirectory(metapath, packagesDir, packages, settings.applicationName(), + settings.applicationVersion()); + + const QString configCopy = metapath + QLatin1String("/installer-config"); + QInstaller::mkdir(configCopy); + QString absoluteConfigPath = QFileInfo(configdir).absoluteFilePath(); + + QDirIterator it(absoluteConfigPath, QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + while (it.hasNext()) { + const QString next = it.next(); + if (next.contains(QLatin1String("/."))) // skip files that are in directories starting with a point + continue; + + qDebug() << "\tFound configuration file: " << next; + const QFileInfo sourceFileInfo(next); + const QString source = sourceFileInfo.absoluteFilePath(); + const QFileInfo targetFileInfo(configCopy, QFileInfo(next).fileName()); + const QDir targetDir = targetFileInfo.dir(); + if (!targetDir.exists()) + QInstaller::mkpath(targetFileInfo.absolutePath()); + const QString target = targetFileInfo.absoluteFilePath(); + + if (!QFile::copy(source, target)) + throw Error(QObject::tr("Could not copy %1.").arg(source)); + + if (sourceFileInfo.fileName().toLower() == QLatin1String("config.xml")) { + // if we just copied the config.xml, make sure to remove the RSA private key from it :-o + QFile configXml(targetDir.filePath(QLatin1String("config.xml"))); + configXml.open(QIODevice::ReadOnly); + QDomDocument dom; + dom.setContent(&configXml); + configXml.close(); + + // iterate over all child elements, searching for relative file names + const QDomNodeList children = dom.documentElement().childNodes(); + for (int i = 0; i < children.count(); ++i) { + QDomElement el = children.at(i).toElement(); + if (el.isNull()) + continue; + + QFileInfo fi(absoluteConfigPath, el.text()); +#if defined(Q_OS_MAC) + const QFileInfo fiIcon(absoluteConfigPath, el.text() + QLatin1String(".icns")); +#elif defined(Q_OS_WIN) + const QFileInfo fiIcon(absoluteConfigPath, el.text() + QLatin1String(".ico")); +#else + const QFileInfo fiIcon(absoluteConfigPath, el.text() + QLatin1String(".png")); +#endif + if (!fi.exists() && fiIcon.exists()) + fi = fiIcon; + + if (!fi.exists() || fi.absolutePath() == QFileInfo(configdir).dir().absolutePath()) + continue; + + if (fi.isDir()) + continue; + + const QString newName = el.text().replace(QRegExp(QLatin1String("\\\\|/|\\.")), + QLatin1String("_")); + + if (!QFile::exists(targetDir.absoluteFilePath(newName))) { + if (!QFile::copy(fi.absoluteFilePath(), targetDir.absoluteFilePath(newName))) + throw Error(QObject::tr("Could not copy %1.").arg(el.text())); + } + el.removeChild(el.firstChild()); + el.appendChild(dom.createTextNode(newName)); + } + + openForWrite(&configXml, configXml.fileName()); + QTextStream stream(&configXml); + dom.save(stream, 4); + qDebug() << "\tdone."; + } + } + return metapath; +} + +static int printErrorAndUsageAndExit(const QString &err) +{ + std::cerr << qPrintable(err) << std::endl << std::endl; + printUsage(); + return EXIT_FAILURE; +} + +/*! + Usage: + binarycreator: [--help|-h] [-p|--packages packages directory] [-t|--template binary] + -c|--config confdir target component ... + template defaults to installerbase[.exe] in the same directory +*/ +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + QInstaller::init(); + + QString templateBinary = QLatin1String("installerbase"); +#ifdef Q_OS_WIN + templateBinary += QLatin1String(".exe"); +#endif + if (!QFileInfo(templateBinary).exists()) + templateBinary = QString::fromLatin1("%1/%2").arg(qApp->applicationDirPath(), templateBinary); + + QString target; + QString configDir; + QString packagesDirectory = QDir::currentPath(); + bool nodeps = false; + bool offlineOnly = false; + QStringList resources; + QStringList components; + 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(QObject::tr("Error: Packages parameter missing argument.")); + } + if (!QFileInfo(*it).exists()) { + return printErrorAndUsageAndExit(QObject::tr("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(QObject::tr("Error: --include and --exclude are mutually " + "exclusive. Use either one or the other.")); + if (it == args.end() || it->startsWith(QLatin1String("-"))) + return printErrorAndUsageAndExit(QObject::tr("Error: Package to exclude missing.")); + filteredPackages = it->split(QLatin1Char(',')); + } else if (*it == QLatin1String("-i") || *it == QLatin1String("--include")) { + ++it; + if (!filteredPackages.isEmpty()) + return printErrorAndUsageAndExit(QObject::tr("Error: --include and --exclude are mutually " + "exclusive. Use either one or the other.")); + if (it == args.end() || it->startsWith(QLatin1String("-"))) + return printErrorAndUsageAndExit(QObject::tr("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("--nodeps")) { + if (!filteredPackages.isEmpty()) + return printErrorAndUsageAndExit(QObject::tr("for the --include and --exclude case you also " + "have to ensure that nopdeps==false")); + nodeps = true; + } else if (*it == QLatin1String("--offline-only")) { + offlineOnly = true; + } else if (*it == QLatin1String("-t") || *it == QLatin1String("--template")) { + ++it; + if (it == args.end()) { + return printErrorAndUsageAndExit(QObject::tr("Error: Template parameter missing argument.")); + } + if (!QFileInfo(*it).exists()) { + return printErrorAndUsageAndExit(QObject::tr("Error: Template not found at the specified " + "location.")); + } + templateBinary = *it; + } else if (*it == QLatin1String("-c") || *it == QLatin1String("--config")) { + ++it; + if (it == args.end()) + return printErrorAndUsageAndExit(QObject::tr("Error: Config parameter missing argument.")); + const QFileInfo fi(*it); + if (!fi.exists()) { + return printErrorAndUsageAndExit(QObject::tr("Error: Config directory %1 not found at the " + "specified location.").arg(*it)); + } + if (!fi.isDir()) { + return printErrorAndUsageAndExit(QObject::tr("Error: Configuration %1 is not a directory.") + .arg(*it)); + } + if (!fi.isReadable()) { + return printErrorAndUsageAndExit(QObject::tr("Error: Config directory %1 is not readable.") + .arg(*it)); + } + configDir = *it; + } else if (*it == QLatin1String("-r") || *it == QLatin1String("--resources")) { + ++it; + if (it == args.end() || it->startsWith(QLatin1String("-"))) + return printErrorAndUsageAndExit(QObject::tr("Error: Resource files to include missing.")); + resources = it->split(QLatin1Char(',')); + } else if (*it == QLatin1String("--ignore-translations") + || *it == QLatin1String("--ignore-invalid-packages")) { + continue; + } else { + if (target.isEmpty()) + target = *it; + else + components.append(*it); + } + } + + if (!components.isEmpty()) { + std::cout << "Package names at the end of the command are deprecated" + " - please use --include or --exclude" << std::endl; + if (nodeps) { + filteredPackages.append(components); + ftype = QInstallerTools::Include; + } + } + + if (target.isEmpty()) + return printErrorAndUsageAndExit(QObject::tr("Error: Target parameter missing.")); + + if (configDir.isEmpty()) + return printErrorAndUsageAndExit(QObject::tr("Error: No configuration directory selected.")); + + qDebug() << "Parsed arguments, ok."; + + try { + QInstallerTools::PackageInfoVector packages = createListOfPackages(packagesDirectory, + filteredPackages, ftype); + const QString metaDir = createMetaDataDirectory(packages, packagesDirectory, configDir); + { + QSettings confInternal(metaDir + QLatin1String("/config/config-internal.ini") + , QSettings::IniFormat); + 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"); + } +#elif defined(Q_OS_WIN) + // on windows, we add .exe + if (!target.endsWith(QLatin1String(".exe"))) + target += QLatin1String(".exe"); +#endif + int result = EXIT_FAILURE; + { + Input input; + input.outputPath = target; + input.installerExePath = templateBinary; + input.binaryResourcePath = createBinaryResourceFile(metaDir); + input.binaryResources = createBinaryResourceFiles(resources); + + QInstallerTools::copyComponentData(packagesDirectory, metaDir, 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("\tAppending %1 (%2 bytes)").arg(archive, + QString::number(arch->size())); + comp.appendArchive(arch); + } + input.components.insertComponent(comp); + } + + qDebug() << "Creating the binary"; + result = assemble(input, configDir); + + // cleanup + qDebug() << "Cleaning up..."; + QFile::remove(input.binaryResourcePath); + foreach (const QString &resource, input.binaryResources) + QFile::remove(resource); + } + removeDirectory(metaDir); + return result; + } catch (const Error &e) { + std::cerr << e.message() << std::endl; + return EXIT_FAILURE; + } catch (...) { + std::cerr << "Unknown exception caught" << std::endl; + return EXIT_FAILURE; + } + return EXIT_FAILURE; +} diff --git a/tools/binarycreator/binarycreator.pro b/tools/binarycreator/binarycreator.pro new file mode 100644 index 000000000..b6f164b4d --- /dev/null +++ b/tools/binarycreator/binarycreator.pro @@ -0,0 +1,20 @@ +TEMPLATE = app +TARGET = binarycreator +DEPENDPATH += . .. rcc ../common +INCLUDEPATH += . .. rcc ../common + +include(../../installerfw.pri) + +QT -= gui +LIBS += -linstaller + +CONFIG += console +CONFIG -= app_bundle +DESTDIR = $$IFW_APP_PATH + +SOURCES = binarycreator.cpp \ + rcc.cpp \ + rccmain.cpp \ + repositorygen.cpp +HEADERS = rcc.h +RESOURCES += binarycreator.qrc
\ No newline at end of file diff --git a/tools/binarycreator/binarycreator.qrc b/tools/binarycreator/binarycreator.qrc new file mode 100644 index 000000000..a32a88e27 --- /dev/null +++ b/tools/binarycreator/binarycreator.qrc @@ -0,0 +1,8 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/default_icon_mac.icns</file> + <file>resources/copylibsintobundle.sh</file> + <file>resources/mkdmg.sh</file> + <file alias="resources/installerbase.ico">../../src/sdk/installerbase.ico</file> +</qresource> +</RCC> diff --git a/tools/binarycreator/rcc/qcorecmdlineargs_p.h b/tools/binarycreator/rcc/qcorecmdlineargs_p.h new file mode 100644 index 000000000..0f6c48dbb --- /dev/null +++ b/tools/binarycreator/rcc/qcorecmdlineargs_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** 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, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCORECMDLINEARGS_P_H +#define QCORECMDLINEARGS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtCore/qstring.h" +#include "QtCore/qstringlist.h" + +QT_BEGIN_NAMESPACE + +static inline QStringList qCmdLineArgs(int argc, char *argv[]) +{ + QStringList args; + for (int i = 0; i != argc; ++i) + args += QString::fromLocal8Bit(argv[i]); + return args; +} + + +QT_END_NAMESPACE + +#endif // QCORECMDLINEARGS_WIN_P_H diff --git a/tools/binarycreator/rcc/rcc.cpp b/tools/binarycreator/rcc/rcc.cpp new file mode 100644 index 000000000..de1cfc566 --- /dev/null +++ b/tools/binarycreator/rcc/rcc.cpp @@ -0,0 +1,958 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2009-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "rcc.h" + +#include <QtCore/QByteArray> +#include <QtCore/QDateTime> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QDirIterator> +#include <QtCore/QFile> +#include <QtCore/QIODevice> +#include <QtCore/QLocale> +#include <QtCore/QStack> + +#include <QtXml/QDomDocument> + +QT_BEGIN_NAMESPACE + +enum { + CONSTANT_USENAMESPACE = 1, + CONSTANT_COMPRESSLEVEL_DEFAULT = -1, + CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 +}; + + +#define writeString(s) write(s, sizeof(s)) + +void RCCResourceLibrary::write(const char *str, int len) +{ + --len; // trailing \0 on string literals... + int n = m_out.size(); + m_out.resize(n + len); + memcpy(m_out.data() + n, str, len); +} + +void RCCResourceLibrary::writeByteArray(const QByteArray &other) +{ + m_out.append(other); +} + +static inline QString msgOpenReadFailed(const QString &fname, const QString &why) +{ + return QString::fromUtf8("Unable to open %1 for reading: %2\n").arg(fname).arg(why); +} + + +/////////////////////////////////////////////////////////// +// +// RCCFileInfo +// +/////////////////////////////////////////////////////////// + +class RCCFileInfo +{ +public: + enum Flags + { + NoFlags = 0x00, + Compressed = 0x01, + Directory = 0x02 + }; + + RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(), + QLocale::Language language = QLocale::C, + QLocale::Country country = QLocale::AnyCountry, + uint flags = NoFlags, + int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT, + int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT); + ~RCCFileInfo(); + + QString resourceName() const; + +public: + qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage); + qint64 writeDataName(RCCResourceLibrary &, qint64 offset); + void writeDataInfo(RCCResourceLibrary &lib); + + int m_flags; + QString m_name; + QLocale::Language m_language; + QLocale::Country m_country; + QFileInfo m_fileInfo; + RCCFileInfo *m_parent; + QHash<QString, RCCFileInfo*> m_children; + int m_compressLevel; + int m_compressThreshold; + + qint64 m_nameOffset; + qint64 m_dataOffset; + qint64 m_childOffset; +}; + +RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, + QLocale::Language language, QLocale::Country country, uint flags, + int compressLevel, int compressThreshold) +{ + m_name = name; + m_fileInfo = fileInfo; + m_language = language; + m_country = country; + m_flags = flags; + m_parent = 0; + m_nameOffset = 0; + m_dataOffset = 0; + m_childOffset = 0; + m_compressLevel = compressLevel; + m_compressThreshold = compressThreshold; +} + +RCCFileInfo::~RCCFileInfo() +{ + qDeleteAll(m_children); +} + +QString RCCFileInfo::resourceName() const +{ + QString resource = m_name; + for (RCCFileInfo *p = m_parent; p; p = p->m_parent) + resource = resource.prepend(p->m_name + QLatin1Char('/')); + return QLatin1Char(':') + resource; +} + +void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) +{ + const bool text = (lib.m_format == RCCResourceLibrary::C_Code); + //some info + if (text) { + if (m_language != QLocale::C) { + lib.writeString(" // "); + lib.writeByteArray(resourceName().toLocal8Bit()); + lib.writeString(" ["); + lib.writeByteArray(QByteArray::number(m_country)); + lib.writeString("::"); + lib.writeByteArray(QByteArray::number(m_language)); + lib.writeString("[\n "); + } else { + lib.writeString(" // "); + lib.writeByteArray(resourceName().toLocal8Bit()); + lib.writeString("\n "); + } + } + + //pointer data + if (m_flags & RCCFileInfo::Directory) { + // name offset + lib.writeNumber4(m_nameOffset); + + // flags + lib.writeNumber2(m_flags); + + // child count + lib.writeNumber4(m_children.size()); + + // first child offset + lib.writeNumber4(m_childOffset); + } else { + // name offset + lib.writeNumber4(m_nameOffset); + + // flags + lib.writeNumber2(m_flags); + + // locale + lib.writeNumber2(m_country); + lib.writeNumber2(m_language); + + //data offset + lib.writeNumber4(m_dataOffset); + } + if (text) + lib.writeChar('\n'); +} + +qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, + QString *errorMessage) +{ + const bool text = (lib.m_format == RCCResourceLibrary::C_Code); + + //capture the offset + m_dataOffset = offset; + + //find the data to be written + QFile file(m_fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) { + *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); + return 0; + } + QByteArray data = file.readAll(); + +#ifndef QT_NO_COMPRESS + // Check if compression is useful for this file + if (m_compressLevel != 0 && data.size() != 0) { + QByteArray compressed = + qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel); + + int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); + if (compressRatio >= m_compressThreshold) { + data = compressed; + m_flags |= Compressed; + } + } +#endif // QT_NO_COMPRESS + + // some info + if (text) { + lib.writeString(" // "); + lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit()); + lib.writeString("\n "); + } + + // write the length + + lib.writeNumber4(data.size()); + if (text) + lib.writeString("\n "); + offset += 4; + + // write the payload + const char *p = data.constData(); + if (text) { + for (int i = data.size(), j = 0; --i >= 0; --j) { + lib.writeHex(*p++); + if (j == 0) { + lib.writeString("\n "); + j = 16; + } + } + } else { + for (int i = data.size(); --i >= 0; ) + lib.writeChar(*p++); + } + offset += data.size(); + + // done + if (text) + lib.writeString("\n "); + return offset; +} + +qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) +{ + const bool text = (lib.m_format == RCCResourceLibrary::C_Code); + + // capture the offset + m_nameOffset = offset; + + // some info + if (text) { + lib.writeString(" // "); + lib.writeByteArray(m_name.toLocal8Bit()); + lib.writeString("\n "); + } + + // write the length + lib.writeNumber2(m_name.length()); + if (text) + lib.writeString("\n "); + offset += 2; + + // write the hash + lib.writeNumber4(qHash(m_name)); + if (text) + lib.writeString("\n "); + offset += 4; + + // write the m_name + const QChar *unicode = m_name.unicode(); + for (int i = 0; i < m_name.length(); ++i) { + lib.writeNumber2(unicode[i].unicode()); + if (text && i % 16 == 0) + lib.writeString("\n "); + } + offset += m_name.length()*2; + + // done + if (text) + lib.writeString("\n "); + return offset; +} + + +/////////////////////////////////////////////////////////// +// +// RCCResourceLibrary +// +/////////////////////////////////////////////////////////// + +RCCResourceLibrary::Strings::Strings() : + TAG_RCC(QLatin1String("RCC")), + TAG_RESOURCE(QLatin1String("qresource")), + TAG_FILE(QLatin1String("file")), + ATTRIBUTE_LANG(QLatin1String("lang")), + ATTRIBUTE_PREFIX(QLatin1String("prefix")), + ATTRIBUTE_ALIAS(QLatin1String("alias")), + ATTRIBUTE_THRESHOLD(QLatin1String("threshold")), + ATTRIBUTE_COMPRESS(QLatin1String("compress")) +{ +} + +RCCResourceLibrary::RCCResourceLibrary() + : m_root(0), + m_format(C_Code), + m_verbose(false), + m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), + m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), + m_treeOffset(0), + m_namesOffset(0), + m_dataOffset(0), + m_useNameSpace(CONSTANT_USENAMESPACE), + m_errorDevice(0) +{ + m_out.reserve(30 * 1000 * 1000); +} + +RCCResourceLibrary::~RCCResourceLibrary() +{ + delete m_root; +} + +bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, + const QString &fname, QString currentPath, bool ignoreErrors) +{ + Q_ASSERT(m_errorDevice); + const QChar slash = QLatin1Char('/'); + if (!currentPath.isEmpty() && !currentPath.endsWith(slash)) + currentPath += slash; + + QDomDocument document; + { + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + if (!document.setContent(inputDevice, &errorMsg, &errorLine, &errorColumn)) { + if (ignoreErrors) + return true; + const QString msg = QString::fromUtf8("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMsg); + m_errorDevice->write(msg.toUtf8()); + return false; + } + } + + QDomElement domRoot = document.firstChildElement(m_strings.TAG_RCC).toElement(); + if (!domRoot.isNull() && domRoot.tagName() == m_strings.TAG_RCC) { + for (QDomNode node = domRoot.firstChild(); !node.isNull(); node = node.nextSibling()) { + if (!node.isElement()) + continue; + + QDomElement child = node.toElement(); + if (!child.isNull() && child.tagName() == m_strings.TAG_RESOURCE) { + QLocale::Language language = QLocale::c().language(); + QLocale::Country country = QLocale::c().country(); + + if (child.hasAttribute(m_strings.ATTRIBUTE_LANG)) { + QString attribute = child.attribute(m_strings.ATTRIBUTE_LANG); + QLocale lang = QLocale(attribute); + language = lang.language(); + if (2 == attribute.length()) { + // Language only + country = QLocale::AnyCountry; + } else { + country = lang.country(); + } + } + + QString prefix; + if (child.hasAttribute(m_strings.ATTRIBUTE_PREFIX)) + prefix = child.attribute(m_strings.ATTRIBUTE_PREFIX); + if (!prefix.startsWith(slash)) + prefix.prepend(slash); + if (!prefix.endsWith(slash)) + prefix += slash; + + for (QDomNode res = child.firstChild(); !res.isNull(); res = res.nextSibling()) { + if (res.isElement() && res.toElement().tagName() == m_strings.TAG_FILE) { + + QString fileName(res.firstChild().toText().data()); + if (fileName.isEmpty()) { + const QString msg = QString::fromUtf8("RCC: Warning: Null node in XML of '%1'\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + } + QString alias; + if (res.toElement().hasAttribute(m_strings.ATTRIBUTE_ALIAS)) + alias = res.toElement().attribute(m_strings.ATTRIBUTE_ALIAS); + else + alias = fileName; + + int compressLevel = m_compressLevel; + if (res.toElement().hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) + compressLevel = res.toElement().attribute(m_strings.ATTRIBUTE_COMPRESS).toInt(); + int compressThreshold = m_compressThreshold; + if (res.toElement().hasAttribute(m_strings.ATTRIBUTE_THRESHOLD)) + compressThreshold = res.toElement().attribute(m_strings.ATTRIBUTE_THRESHOLD).toInt(); + + // Special case for -no-compress. Overrides all other settings. + if (m_compressLevel == -2) + compressLevel = 0; + + alias = QDir::cleanPath(alias); + while (alias.startsWith(QLatin1String("../"))) + alias.remove(0, 3); + alias = QDir::cleanPath(m_resourceRoot) + prefix + alias; + + QString absFileName = fileName; + if (QDir::isRelativePath(absFileName)) + absFileName.prepend(currentPath); + QFileInfo file(absFileName); + if (!file.exists()) { + m_failedResources.push_back(absFileName); + const QString msg = QString::fromUtf8("RCC: Error in '%1': Cannot find file '%2'\n").arg(fname).arg(fileName); + m_errorDevice->write(msg.toUtf8()); + if (ignoreErrors) + continue; + else + return false; + } else if (file.isFile()) { + const bool arc = addFile(alias, RCCFileInfo(alias.section(slash, -1), file, language, country, + RCCFileInfo::NoFlags, compressLevel, compressThreshold)); + if (!arc) + m_failedResources.push_back(absFileName); + } else { + QDir dir; + if (file.isDir()) { + dir.setPath(file.filePath()); + } else { + dir.setPath(file.path()); + dir.setNameFilters(QStringList(file.fileName())); + if (alias.endsWith(file.fileName())) + alias = alias.left(alias.length()-file.fileName().length()); + } + if (!alias.endsWith(slash)) + alias += slash; + QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + QFileInfo child(it.fileInfo()); + if (child.fileName() != QLatin1String(".") && child.fileName() != QLatin1String("..")) { + const bool arc = addFile(alias + child.fileName(), + RCCFileInfo(child.fileName(), child, language, country, + RCCFileInfo::NoFlags, compressLevel, compressThreshold)); + if (!arc) + m_failedResources.push_back(child.fileName()); + } + } + } + } + } + } + } + } + if (m_root == 0) { + const QString msg = QString::fromUtf8("RCC: Warning: No resources in '%1'.\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + if (!ignoreErrors && m_format == Binary) { + // create dummy entry, otherwise loading qith QResource will crash + m_root = new RCCFileInfo(QString(), QFileInfo(), + QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); + } + } + + return true; +} + +bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file) +{ + Q_ASSERT(m_errorDevice); + if (file.m_fileInfo.size() > 0xffffffff) { + const QString msg = QString::fromUtf8("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath()); + m_errorDevice->write(msg.toUtf8()); + return false; + } + if (!m_root) + m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); + + RCCFileInfo *parent = m_root; + const QStringList nodes = alias.split(QLatin1Char('/')); + for (int i = 1; i < nodes.size()-1; ++i) { + const QString node = nodes.at(i); + if (node.isEmpty()) + continue; + if (!parent->m_children.contains(node)) { + RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); + s->m_parent = parent; + parent->m_children.insert(node, s); + parent = s; + } else { + parent = parent->m_children[node]; + } + } + + const QString filename = nodes.at(nodes.size()-1); + RCCFileInfo *s = new RCCFileInfo(file); + s->m_parent = parent; + parent->m_children.insertMulti(filename, s); + return true; +} + +void RCCResourceLibrary::reset() +{ + if (m_root) { + delete m_root; + m_root = 0; + } + m_errorDevice = 0; + m_failedResources.clear(); +} + + +bool RCCResourceLibrary::readFiles(bool ignoreErrors, QIODevice &errorDevice) +{ + reset(); + m_errorDevice = &errorDevice; + //read in data + if (m_verbose) { + const QString msg = QString::fromUtf8("Processing %1 files [%2]\n") + .arg(m_fileNames.size()).arg(static_cast<int>(ignoreErrors)); + m_errorDevice->write(msg.toUtf8()); + } + for (int i = 0; i < m_fileNames.size(); ++i) { + QFile fileIn; + QString fname = m_fileNames.at(i); + QString pwd; + if (fname == QLatin1String("-")) { + fname = QLatin1String("(stdin)"); + pwd = QDir::currentPath(); + fileIn.setFileName(fname); + if (!fileIn.open(stdin, QIODevice::ReadOnly)) { + m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); + return false; + } + } else { + pwd = QFileInfo(fname).path(); + fileIn.setFileName(fname); + if (!fileIn.open(QIODevice::ReadOnly)) { + m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); + return false; + } + } + if (m_verbose) { + const QString msg = QString::fromUtf8("Interpreting %1\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + } + + if (!interpretResourceFile(&fileIn, fname, pwd, ignoreErrors)) + return false; + } + return true; +} + +QStringList RCCResourceLibrary::dataFiles() const +{ + QStringList ret; + QStack<RCCFileInfo*> pending; + + if (!m_root) + return ret; + pending.push(m_root); + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); + it != file->m_children.end(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + ret.append(child->m_fileInfo.filePath()); + } + } + return ret; +} + +// Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion +static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) +{ + typedef QHash<QString, RCCFileInfo*>::const_iterator ChildConstIterator; + const QChar slash = QLatin1Char('/'); + const ChildConstIterator cend = m_root->m_children.constEnd(); + for (ChildConstIterator it = m_root->m_children.constBegin(); it != cend; ++it) { + const RCCFileInfo *child = it.value(); + QString childName = path; + childName += slash; + childName += child->m_name; + if (child->m_flags & RCCFileInfo::Directory) { + resourceDataFileMapRecursion(child, childName, m); + } else { + m.insert(childName, child->m_fileInfo.filePath()); + } + } +} + +RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const +{ + ResourceDataFileMap rc; + if (m_root) + resourceDataFileMapRecursion(m_root, QString(QLatin1Char(':')), rc); + return rc; +} + +bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &errorDevice) +{ + m_errorDevice = &errorDevice; + //write out + if (m_verbose) + m_errorDevice->write("Outputting code\n"); + if (!writeHeader()) { + m_errorDevice->write("Could not write header\n"); + return false; + } + if (m_root) { + if (!writeDataBlobs()) { + m_errorDevice->write("Could not write data blobs.\n"); + return false; + } + if (!writeDataNames()) { + m_errorDevice->write("Could not write file names\n"); + return false; + } + if (!writeDataStructure()) { + m_errorDevice->write("Could not write data tree\n"); + return false; + } + } + if (!writeInitializer()) { + m_errorDevice->write("Could not write footer\n"); + return false; + } + outDevice.write(m_out, m_out.size()); + return true; +} + +void RCCResourceLibrary::writeHex(quint8 tmp) +{ + const char * const digits = "0123456789abcdef"; + writeChar('0'); + writeChar('x'); + if (tmp < 16) { + writeChar(digits[tmp]); + } else { + writeChar(digits[tmp >> 4]); + writeChar(digits[tmp & 0xf]); + } + writeChar(','); +} + +void RCCResourceLibrary::writeNumber2(quint16 number) +{ + if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 8); + writeHex(number); + } +} + +void RCCResourceLibrary::writeNumber4(quint32 number) +{ + if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 24); + writeChar(number >> 16); + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 24); + writeHex(number >> 16); + writeHex(number >> 8); + writeHex(number); + } +} + +bool RCCResourceLibrary::writeHeader() +{ + if (m_format == C_Code) { + writeString("/****************************************************************************\n"); + writeString("** Resource object code\n"); + writeString("**\n"); + writeString("** Created: "); + writeByteArray(QDateTime::currentDateTime().toString().toLatin1()); + writeString("\n** by: The Resource Compiler for Qt version "); + writeByteArray(QT_VERSION_STR); + writeString("\n**\n"); + writeString("** WARNING! All changes made in this file will be lost!\n"); + writeString( "*****************************************************************************/\n\n"); + writeString("#include <QtCore/qglobal.h>\n\n"); + } else if (m_format == Binary) { + writeString("qres"); + writeNumber4(0); + writeNumber4(0); + writeNumber4(0); + writeNumber4(0); + } + return true; +} + +bool RCCResourceLibrary::writeDataBlobs() +{ + Q_ASSERT(m_errorDevice); + if (m_format == C_Code) + writeString("static const unsigned char qt_resource_data[] = {\n"); + else if (m_format == Binary) + m_dataOffset = m_out.size(); + QStack<RCCFileInfo*> pending; + + if (!m_root) + return false; + + pending.push(m_root); + qint64 offset = 0; + QString errorMessage; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); + it != file->m_children.end(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + else { + offset = child->writeDataBlob(*this, offset, &errorMessage); + if (offset == 0) + m_errorDevice->write(errorMessage.toUtf8()); + } + } + } + if (m_format == C_Code) + writeString("\n};\n\n"); + return true; +} + +bool RCCResourceLibrary::writeDataNames() +{ + if (m_format == C_Code) + writeString("static const unsigned char qt_resource_name[] = {\n"); + else if (m_format == Binary) + m_namesOffset = m_out.size(); + + QHash<QString, int> names; + QStack<RCCFileInfo*> pending; + + if (!m_root) + return false; + + pending.push(m_root); + qint64 offset = 0; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); + it != file->m_children.end(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + if (names.contains(child->m_name)) { + child->m_nameOffset = names.value(child->m_name); + } else { + names.insert(child->m_name, offset); + offset = child->writeDataName(*this, offset); + } + } + } + if (m_format == C_Code) + writeString("\n};\n\n"); + return true; +} + +static bool qt_rcc_compare_hash(const RCCFileInfo *left, const RCCFileInfo *right) +{ + return qHash(left->m_name) < qHash(right->m_name); +} + +bool RCCResourceLibrary::writeDataStructure() +{ + if (m_format == C_Code) + writeString("static const unsigned char qt_resource_struct[] = {\n"); + else if (m_format == Binary) + m_treeOffset = m_out.size(); + QStack<RCCFileInfo*> pending; + + if (!m_root) + return false; + + //calculate the child offsets (flat) + pending.push(m_root); + int offset = 1; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + file->m_childOffset = offset; + + //sort by hash value for binary lookup + QList<RCCFileInfo*> m_children = file->m_children.values(); + qSort(m_children.begin(), m_children.end(), qt_rcc_compare_hash); + + //write out the actual data now + for (int i = 0; i < m_children.size(); ++i) { + RCCFileInfo *child = m_children.at(i); + ++offset; + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + } + } + + //write out the structure (ie iterate again!) + pending.push(m_root); + m_root->writeDataInfo(*this); + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + + //sort by hash value for binary lookup + QList<RCCFileInfo*> m_children = file->m_children.values(); + qSort(m_children.begin(), m_children.end(), qt_rcc_compare_hash); + + //write out the actual data now + for (int i = 0; i < m_children.size(); ++i) { + RCCFileInfo *child = m_children.at(i); + child->writeDataInfo(*this); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + } + } + if (m_format == C_Code) + writeString("\n};\n\n"); + + return true; +} + +void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name) +{ + if (m_useNameSpace) { + writeString("QT_MANGLE_NAMESPACE("); + writeByteArray(name); + writeChar(')'); + } else { + writeByteArray(name); + } +} + +void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name) +{ + if (m_useNameSpace) { + writeString("QT_PREPEND_NAMESPACE("); + writeByteArray(name); + writeChar(')'); + } else { + writeByteArray(name); + } +} + +bool RCCResourceLibrary::writeInitializer() +{ + if (m_format == C_Code) { + //write("\nQT_BEGIN_NAMESPACE\n"); + QString initName = m_initName; + if (!initName.isEmpty()) { + initName.prepend(QLatin1Char('_')); + initName.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), QLatin1String("_")); + } + + //init + if (m_useNameSpace) + writeString("QT_BEGIN_NAMESPACE\n\n"); + if (m_root) { + writeString("extern bool qRegisterResourceData\n " + "(int, const unsigned char *, " + "const unsigned char *, const unsigned char *);\n\n"); + writeString("extern bool qUnregisterResourceData\n " + "(int, const unsigned char *, " + "const unsigned char *, const unsigned char *);\n\n"); + } + if (m_useNameSpace) + writeString("QT_END_NAMESPACE\n\n\n"); + QString initResources = QLatin1String("qInitResources"); + initResources += initName; + writeString("int "); + writeMangleNamespaceFunction(initResources.toLatin1()); + writeString("()\n{\n"); + + if (m_root) { + writeString(" "); + writeAddNamespaceFunction("qRegisterResourceData"); + writeString("\n (0x01, qt_resource_struct, " + "qt_resource_name, qt_resource_data);\n"); + } + writeString(" return 1;\n"); + writeString("}\n\n"); + writeString("Q_CONSTRUCTOR_FUNCTION("); + writeMangleNamespaceFunction(initResources.toLatin1()); + writeString(")\n\n"); + + //cleanup + QString cleanResources = QLatin1String("qCleanupResources"); + cleanResources += initName; + writeString("int "); + writeMangleNamespaceFunction(cleanResources.toLatin1()); + writeString("()\n{\n"); + if (m_root) { + writeString(" "); + writeAddNamespaceFunction("qUnregisterResourceData"); + writeString("\n (0x01, qt_resource_struct, " + "qt_resource_name, qt_resource_data);\n"); + } + writeString(" return 1;\n"); + writeString("}\n\n"); + writeString("Q_DESTRUCTOR_FUNCTION("); + writeMangleNamespaceFunction(cleanResources.toLatin1()); + writeString(")\n\n"); + } else if (m_format == Binary) { + int i = 4; + char *p = m_out.data(); + p[i++] = 0; // 0x01 + p[i++] = 0; + p[i++] = 0; + p[i++] = 1; + + p[i++] = (m_treeOffset >> 24) & 0xff; + p[i++] = (m_treeOffset >> 16) & 0xff; + p[i++] = (m_treeOffset >> 8) & 0xff; + p[i++] = (m_treeOffset >> 0) & 0xff; + + p[i++] = (m_dataOffset >> 24) & 0xff; + p[i++] = (m_dataOffset >> 16) & 0xff; + p[i++] = (m_dataOffset >> 8) & 0xff; + p[i++] = (m_dataOffset >> 0) & 0xff; + + p[i++] = (m_namesOffset >> 24) & 0xff; + p[i++] = (m_namesOffset >> 16) & 0xff; + p[i++] = (m_namesOffset >> 8) & 0xff; + p[i++] = (m_namesOffset >> 0) & 0xff; + } + return true; +} + +QT_END_NAMESPACE diff --git a/tools/binarycreator/rcc/rcc.h b/tools/binarycreator/rcc/rcc.h new file mode 100644 index 000000000..205d1a8e9 --- /dev/null +++ b/tools/binarycreator/rcc/rcc.h @@ -0,0 +1,144 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2009-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef RCC_H +#define RCC_H + +#include <QtCore/QStringList> +#include <QtCore/QHash> +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE + +class RCCFileInfo; +class QIODevice; +class QTextStream; + + +class RCCResourceLibrary +{ + RCCResourceLibrary(const RCCResourceLibrary &); + RCCResourceLibrary &operator=(const RCCResourceLibrary &); + +public: + RCCResourceLibrary(); + ~RCCResourceLibrary(); + + bool output(QIODevice &out, QIODevice &errorDevice); + + bool readFiles(bool ignoreErrors, QIODevice &errorDevice); + + enum Format { Binary, C_Code }; + void setFormat(Format f) { m_format = f; } + Format format() const { return m_format; } + + void setInputFiles(const QStringList &files) { m_fileNames = files; } + QStringList inputFiles() const { return m_fileNames; } + + QStringList dataFiles() const; + + // Return a map of resource identifier (':/newPrefix/images/p1.png') to file. + typedef QHash<QString, QString> ResourceDataFileMap; + ResourceDataFileMap resourceDataFileMap() const; + + void setVerbose(bool b) { m_verbose = b; } + bool verbose() const { return m_verbose; } + + void setInitName(const QString &name) { m_initName = name; } + QString initName() const { return m_initName; } + + void setCompressLevel(int c) { m_compressLevel = c; } + int compressLevel() const { return m_compressLevel; } + + void setCompressThreshold(int t) { m_compressThreshold = t; } + int compressThreshold() const { return m_compressThreshold; } + + void setResourceRoot(const QString &root) { m_resourceRoot = root; } + QString resourceRoot() const { return m_resourceRoot; } + + void setUseNameSpace(bool v) { m_useNameSpace = v; } + bool useNameSpace() const { return m_useNameSpace; } + + QStringList failedResources() const { return m_failedResources; } + +private: + struct Strings { + Strings(); + const QString TAG_RCC; + const QString TAG_RESOURCE; + const QString TAG_FILE; + const QString ATTRIBUTE_LANG; + const QString ATTRIBUTE_PREFIX; + const QString ATTRIBUTE_ALIAS; + const QString ATTRIBUTE_THRESHOLD; + const QString ATTRIBUTE_COMPRESS; + }; + friend class RCCFileInfo; + void reset(); + bool addFile(const QString &alias, const RCCFileInfo &file); + bool interpretResourceFile(QIODevice *inputDevice, const QString &file, + QString currentPath = QString(), bool ignoreErrors = false); + bool writeHeader(); + bool writeDataBlobs(); + bool writeDataNames(); + bool writeDataStructure(); + bool writeInitializer(); + void writeMangleNamespaceFunction(const QByteArray &name); + void writeAddNamespaceFunction(const QByteArray &name); + void writeHex(quint8 number); + void writeNumber2(quint16 number); + void writeNumber4(quint32 number); + void writeChar(char c) { m_out.append(c); } + void writeByteArray(const QByteArray &); + void write(const char *, int len); + + const Strings m_strings; + RCCFileInfo *m_root; + QStringList m_fileNames; + QString m_resourceRoot; + QString m_initName; + Format m_format; + bool m_verbose; + int m_compressLevel; + int m_compressThreshold; + int m_treeOffset; + int m_namesOffset; + int m_dataOffset; + bool m_useNameSpace; + QStringList m_failedResources; + QIODevice *m_errorDevice; + QByteArray m_out; +}; + +QT_END_NAMESPACE + +#endif // RCC_H diff --git a/tools/binarycreator/rcc/rccmain.cpp b/tools/binarycreator/rcc/rccmain.cpp new file mode 100644 index 000000000..4256d5db4 --- /dev/null +++ b/tools/binarycreator/rcc/rccmain.cpp @@ -0,0 +1,248 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2009-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include <rcc.h> +#include "qcorecmdlineargs_p.h" + +#include <QDebug> +#include <QDir> +#include <QFile> +#include <QFileInfo> +#include <QTextStream> + +QT_BEGIN_NAMESPACE + +void showHelp(const QString &argv0, const QString &error) +{ + fprintf(stderr, "Qt resource compiler\n"); + if (!error.isEmpty()) + fprintf(stderr, "%s: %s\n", qPrintable(argv0), qPrintable(error)); + fprintf(stderr, "Usage: %s [options] <inputs>\n\n" + "Options:\n" + " -o file write output to file rather than stdout\n" + " -name name create an external initialization function with name\n" + " -threshold level threshold to consider compressing files\n" + " -compress level compress input files by level\n" + " -root path prefix resource access path with root path\n" + " -no-compress disable all compression\n" + " -binary output a binary file for use as a dynamic resource\n" + " -namespace turn off namespace macros\n" + " -project Output a resource file containing all\n" + " files from the current directory\n" + " -version display version\n" + " -help display this information\n", + qPrintable(argv0)); +} + +void dumpRecursive(const QDir &dir, QTextStream &out) +{ + QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot + | QDir::NoSymLinks); + foreach (QFileInfo entry, entries) { + if (entry.isDir()) { + dumpRecursive(entry.filePath(), out); + } else { + out << QLatin1String("<file>") + << entry.filePath() + << QLatin1String("</file>\n"); + } + } +} + +int createProject(const QString &outFileName) +{ + QDir currentDir = QDir::current(); + QString currentDirName = currentDir.dirName(); + if (currentDirName.isEmpty()) + currentDirName = QLatin1String("root"); + + QFile file; + bool isOk = false; + if (outFileName.isEmpty()) { + isOk = file.open(stdout, QFile::WriteOnly | QFile::Text); + } else { + file.setFileName(outFileName); + isOk = file.open(QFile::WriteOnly | QFile::Text); + } + if (!isOk) { + fprintf(stderr, "Unable to open %s: %s\n", + outFileName.isEmpty() ? qPrintable(outFileName) : "standard output", + qPrintable(file.errorString())); + return 1; + } + + QTextStream out(&file); + out << QLatin1String("<!DOCTYPE RCC><RCC version=\"1.0\">\n" + "<qresource>\n"); + + // use "." as dir to get relative file pathes + dumpRecursive(QDir(QLatin1String(".")), out); + + out << QLatin1String("</qresource>\n" + "</RCC>\n"); + + return 0; +} + +int runRcc(int argc, char *argv[]) +{ + QString outFilename; + bool helpRequested = false; + bool list = false; + bool projectRequested = false; + QStringList filenamesIn; + + QStringList args = qCmdLineArgs(argc, argv); + + RCCResourceLibrary library; + + //parse options + QString errorMsg; + for (int i = 1; i < args.count() && errorMsg.isEmpty(); i++) { + if (args[i].isEmpty()) + continue; + if (args[i][0] == QLatin1Char('-')) { // option + QString opt = args[i]; + if (opt == QLatin1String("-o")) { + if (!(i < argc-1)) { + errorMsg = QLatin1String("Missing output name"); + break; + } + outFilename = args[++i]; + } else if (opt == QLatin1String("-name")) { + if (!(i < argc-1)) { + errorMsg = QLatin1String("Missing target name"); + break; + } + library.setInitName(args[++i]); + } else if (opt == QLatin1String("-root")) { + if (!(i < argc-1)) { + errorMsg = QLatin1String("Missing root path"); + break; + } + library.setResourceRoot(QDir::cleanPath(args[++i])); + if (library.resourceRoot().isEmpty() + || library.resourceRoot().at(0) != QLatin1Char('/')) + errorMsg = QLatin1String("Root must start with a /"); + } else if (opt == QLatin1String("-compress")) { + if (!(i < argc-1)) { + errorMsg = QLatin1String("Missing compression level"); + break; + } + library.setCompressLevel(args[++i].toInt()); + } else if (opt == QLatin1String("-threshold")) { + if (!(i < argc-1)) { + errorMsg = QLatin1String("Missing compression threshold"); + break; + } + library.setCompressThreshold(args[++i].toInt()); + } else if (opt == QLatin1String("-binary")) { + library.setFormat(RCCResourceLibrary::Binary); + } else if (opt == QLatin1String("-namespace")) { + library.setUseNameSpace(!library.useNameSpace()); + } else if (opt == QLatin1String("-verbose")) { + library.setVerbose(true); + } else if (opt == QLatin1String("-list")) { + list = true; + } else if (opt == QLatin1String("-version") || opt == QLatin1String("-v")) { + fprintf(stderr, "Qt Resource Compiler version %s\n", QT_VERSION_STR); + return 1; + } else if (opt == QLatin1String("-help") || opt == QLatin1String("-h")) { + helpRequested = true; + } else if (opt == QLatin1String("-no-compress")) { + library.setCompressLevel(-2); + } else if (opt == QLatin1String("-project")) { + projectRequested = true; + } else { + errorMsg = QString::fromLatin1("Unknown option: '%1'").arg(args[i]); + } + } else { + if (!QFile::exists(args[i])) { + qWarning("%s: File does not exist '%s'", + qPrintable(args[0]), qPrintable(args[i])); + return 1; + } + filenamesIn.append(args[i]); + } + } + + if (projectRequested && !helpRequested) { + return createProject(outFilename); + } + + if (!filenamesIn.size() || !errorMsg.isEmpty() || helpRequested) { + showHelp(args[0], errorMsg); + return 1; + } + QFile errorDevice; + errorDevice.open(stderr, QIODevice::WriteOnly|QIODevice::Text); + + if (library.verbose()) + errorDevice.write("Qt resource compiler\n"); + + library.setInputFiles(filenamesIn); + + if (!library.readFiles(list, errorDevice)) + return 1; + + // open output + QFile out; + QIODevice::OpenMode mode = QIODevice::WriteOnly; + if (library.format() == RCCResourceLibrary::C_Code) + mode |= QIODevice::Text; + + if (outFilename.isEmpty() || outFilename == QLatin1String("-")) { + // using this overload close() only flushes. + out.open(stdout, mode); + } else { + out.setFileName(outFilename); + if (!out.open(mode)) { + const QString msg = QString::fromUtf8("Unable to open %1 for writing: %2\n").arg(outFilename).arg(out.errorString()); + errorDevice.write(msg.toUtf8()); + return 1; + } + } + + // do the task + if (list) { + const QStringList data = library.dataFiles(); + for (int i = 0; i < data.size(); ++i) { + out.write(qPrintable(QDir::cleanPath(data.at(i)))); + out.write("\n"); + } + return 0; + } + + return library.output(out, errorDevice) ? 0 : 1; +} + +QT_END_NAMESPACE diff --git a/tools/binarycreator/resources/copylibsintobundle.sh b/tools/binarycreator/resources/copylibsintobundle.sh new file mode 100644 index 000000000..2b997f9b5 --- /dev/null +++ b/tools/binarycreator/resources/copylibsintobundle.sh @@ -0,0 +1,161 @@ +#!/bin/sh + +# this script puts all libs directly needed by the bundle into it + +QTDIR="" +IS_DEBUG=0 +HAVE_CORE=0 +HAVE_SVG=0 +HAVE_PHONON=0 +HAVE_SCRIPT=0 +HAVE_SQL=0 +HAVE_WEBKIT=0 + +function handleFile() +{ + local FILE=$1 + local BUNDLE=$2 + + # all dynamic libs directly needed by the bundle, which are not in /System/Library or in /usr/lib (which are system default libs, which we don't want) + local LIBS=`otool -L $FILE | grep -v 'executable_path' | grep -v '/System/Library' | grep -v '/usr/lib' | grep '/' | sed -ne 's,^ *\(.*\) (.*,\1,p'` + + local lib + for lib in $LIBS; do + local NAME=`basename $lib` + + if echo $NAME | grep 'QtCore' >/dev/null; then + HAVE_CORE=1 + QTDIR=`echo $lib | sed -ne 's,^\(.*\)/lib/[^/]*QtCore.*$,\1,p'` + if echo $NAME | grep 'debug' >/dev/null; then + IS_DEBUG=1 + fi + elif echo $NAME | grep 'QtSvg' >/dev/null; then + HAVE_SVG=1 + elif echo $NAME | grep 'phonon' >/dev/null; then + HAVE_PHONON=1 + elif echo $NAME | grep 'QtScript' >/dev/null; then + HAVE_SCRIPT=1 + elif echo $NAME | grep 'QtSql' >/dev/null; then + HAVE_SQL=1 + elif echo $NAME | grep 'QtWebKit' >/dev/null; then + HAVE_WEBKIT=1 + fi + + if [ `basename $FILE` != $NAME ]; then + + # this part handles libraries which are Mac OS X frameworks + if echo $lib | grep '\.framework' >/dev/null; then + local FRAMEWORKPATH=`echo $lib | sed -ne 's,\(.*\.framework\).*,\1,p'` + local FRAMEWORKNAME=`basename $FRAMEWORKPATH` + local NEWFRAMEWORKPATH=`echo $lib | sed -ne "s,.*\($FRAMEWORKNAME\),\1,p"` + + # Qt installed via the precompled binaries... + if [ $FRAMEWORKPATH = $FRAMEWORKNAME ]; then + FRAMEWORKPATH="/Library/Frameworks/$FRAMEWORKNAME" + if [ ! -e "$FRAMEWORKPATH" ]; then + echo "Framework $FRAMEWORKNAME not found." + exit 1 + fi + fi + + if [ ! -e "$BUNDLE/Contents/Frameworks/$NEWFRAMEWORKPATH" ]; then + echo Embedding framework $FRAMEWORKNAME + + + # copy the framework into the bundle + cp -R $FRAMEWORKPATH $BUNDLE/Contents/Frameworks + # remove debug libs we've copied + find $BUNDLE/Contents/Frameworks/$FRAMEWORKNAME -regex '.*_debug\(\.dSYM\)*' | xargs rm -rf + + handleFile "$BUNDLE/Contents/Frameworks/$NEWFRAMEWORKPATH" "$BUNDLE" + fi + # and inform the dynamic linker about this + install_name_tool -change $lib @executable_path/../Frameworks/$NEWFRAMEWORKPATH $FILE + + + # this part handles 'normal' dynamic libraries (.dylib) + else + if [ ! -e "$BUNDLE/Contents/Frameworks/$NAME" ]; then + echo Embedding library $NAME + + # Qt installed via the precompled binaries... + if [ $lib = $NAME ]; then + lib="/Library/Frameworks/$NAME" + if [ ! -e "$lib" ]; then + lib="/usr/lib/$NAME" + fi + if [ ! -e "$lib" ]; then + echo "Library $NAME not found." + exit 1 + fi + fi + + # copy the lib into the bundle + cp $lib $BUNDLE/Contents/Frameworks + handleFile "$BUNDLE/Contents/Frameworks/$NAME" "$BUNDLE" + fi + + # and inform the dynamic linker about this + install_name_tool -change $lib @executable_path/../Frameworks/$NAME $FILE + fi + + fi + done +} + +function handleQtPlugins() +{ + local PLUGINPATH=$QTDIR/plugins + + # QTDIR was not found, then we're using /Developer/Applications/Qt + if [ "$PLUGINPATH" = "/plugins" ]; then + PLUGINPATH="/Developer/Applications/Qt/plugins" + fi + + CLASS=$1 + EXECUTABLE=$2 + BUNDLE=$3 + mkdir -p $BUNDLE/Contents/plugins/$CLASS + echo Add $CLASS plugins + for plugin in `ls $PLUGINPATH/$CLASS/*`; do + plugin=`basename $plugin` + if echo $plugin | grep 'debug' >/dev/null; then + #if [ $IS_DEBUG -eq 1 ]; then + cp "$PLUGINPATH/$CLASS/$plugin" $BUNDLE/Contents/plugins/$CLASS + install_name_tool -change $plugin @executable_path/../plugins/$CLASS/$plugin $EXECUTABLE + handleFile $BUNDLE/Contents/plugins/$CLASS/$plugin $BUNDLE + #fi + else + #if [ $IS_DEBUG -eq 0 ]; then + cp "$PLUGINPATH/$CLASS/$plugin" $BUNDLE/Contents/plugins/$CLASS + install_name_tool -change $plugin @executable_path/../plugins/$CLASS/$plugin $EXECUTABLE + handleFile $BUNDLE/Contents/plugins/$CLASS/$plugin $BUNDLE + #fi + fi + done +} + +# the app bundle we're working with +BUNDLE=$1 +# the executable inside of the bundle +EXECUTABLE=$BUNDLE/Contents/MacOS/`xargs < $BUNDLE/Contents/Info.plist | sed -ne 's,.*<key>CFBundleExecutable</key> <string>\([^<]*\)</string>.*,\1,p'` + +mkdir -p $BUNDLE/Contents/Frameworks + +handleFile $EXECUTABLE $BUNDLE + +if [ $HAVE_CORE -eq 1 ]; then + handleQtPlugins "imageformats" "$EXECUTABLE" "$BUNDLE" +fi +if [ $HAVE_SVG -eq 1 ]; then + handleQtPlugins "iconengines" "$EXECUTABLE" "$BUNDLE" +fi +if [ $HAVE_PHONON -eq 1 ]; then + handleQtPlugins "phonon_backend" "$EXECUTABLE" "$BUNDLE" +fi +if [ $HAVE_SQL -eq 1 ]; then + handleQtPlugins "sqldrivers" "$EXECUTABLE" "$BUNDLE" +fi +if [ $HAVE_WEBKIT -eq 1 ]; then + handleQtPlugins "codecs" "$EXECUTABLE" "$BUNDLE" +fi diff --git a/tools/binarycreator/resources/default_icon_mac.icns b/tools/binarycreator/resources/default_icon_mac.icns Binary files differnew file mode 100644 index 000000000..8d870d649 --- /dev/null +++ b/tools/binarycreator/resources/default_icon_mac.icns diff --git a/tools/binarycreator/resources/mkdmg.sh b/tools/binarycreator/resources/mkdmg.sh new file mode 100644 index 000000000..46606c125 --- /dev/null +++ b/tools/binarycreator/resources/mkdmg.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# +# Creates a disk image (dmg) on Mac OS X from the command line. +# usage: +# mkdmg <volname> <vers> <srcdir> +# +# Where <volname> is the name to use for the mounted image, <vers> is the version +# number of the volume and <srcdir> is where the contents to put on the dmg are. +# +# The result will be a file called <volname>-<vers>.dmg + +if [ $# != 2 ]; then + echo "usage: mkdmg.sh volname srcdir" + exit 0 +fi + +VOL="$1" +FILES="$2" +PATHNAME=`dirname $FILES` + +DMG=`mktemp "/tmp/$VOL.XXXXXX.dmg"` + +# create temporary disk image and format, ejecting when done +SIZE=`du -sk ${FILES} | sed -n 's,^\([0-9]*\).*,\1,p'` +SIZE=$((${SIZE}/1000+1)) +hdiutil create "$DMG" -megabytes ${SIZE} -ov -volname "$VOL" -type UDIF -fs HFS+ >/dev/null +DISK=`hdid "$DMG" | sed -ne 's,^\(.*\) *Apple_H.*,\1,p'` +MOUNT=`hdid "$DMG" | sed -ne 's,^.*Apple_HFS[^/]*\(/.*\)$,\1,p'` + +# mount and copy files onto volume +cp -R "$PATHNAME/`basename $FILES`" "$MOUNT" +hdiutil eject $DISK >/dev/null + +# convert to compressed image, delete temp image +rm -f "$PATHNAME/${VOL}.dmg" +hdiutil convert "$DMG" -format UDZO -o "$PATHNAME/${VOL}.dmg" >/dev/null +rm -f "$DMG" diff --git a/tools/common/repositorygen.cpp b/tools/common/repositorygen.cpp new file mode 100644 index 000000000..84ed0e658 --- /dev/null +++ b/tools/common/repositorygen.cpp @@ -0,0 +1,622 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "repositorygen.h" + +#include <fileutils.h> +#include <errors.h> +#include <lib7z_facade.h> +#include <settings.h> + +#include <kdupdater.h> + +#include <QtCore/QCryptographicHash> +#include <QtCore/QDirIterator> + +#include <QtXml/QDomDocument> + +#include <iostream> + +using namespace QInstallerTools; + +void QInstallerTools::printRepositoryGenOptions() +{ + std::cout << " -c|--config dir The directory containing the installer configuration" << std::endl; + + std::cout << " -p|--packages dir The directory containing the available packages." << std::endl; + std::cout << " Defaults to the current working directory." << std::endl; + + std::cout << " -e|--exclude p1,...,pn Exclude the given packages." << std::endl; + std::cout << " -i|--include p1,...,pn Include the given packages and their dependencies from the " + << "repository." << std::endl; + std::cout << " --ignore-translations Don't use any translation" << std::endl; + std::cout << " --ignore-invalid-packages Ignore all invalid packages instead of aborting." << std::endl; +} + +void QInstallerTools::compressDirectory(const QStringList &paths, const QString &archivePath) +{ + foreach (const QString &path, paths) { + if (!QFileInfo(path).exists()) + throw QInstaller::Error(QObject::tr("Folder %1 does not exist.").arg(path)); + } + + QFile archive(archivePath); + QInstaller::openForWrite(&archive, archivePath); + Lib7z::createArchive(&archive, paths); +} + +void QInstallerTools::compressMetaDirectories(const QString &repoDir) +{ + QDir dir(repoDir); + const QStringList sub = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QString &i, sub) { + QDir sd(dir); + sd.cd(i); + const QString absPath = sd.absolutePath(); + const QString fn = QLatin1String("meta.7z"); + const QString tmpTarget = repoDir + QLatin1String("/") +fn; + compressDirectory(QStringList() << absPath, tmpTarget); + QFile tmp(tmpTarget); + const QString finalTarget = absPath + QLatin1String("/") + fn; + if (!tmp.rename(finalTarget)) { + throw QInstaller::Error(QObject::tr("Could not move %1 to %2").arg(tmpTarget, + finalTarget)); + } + } +} + +void QInstallerTools::generateMetaDataDirectory(const QString &outDir, const QString &dataDir, + const PackageInfoVector &packages, const QString &appName, const QString &appVersion, + const QString &redirectUpdateUrl) +{ + QString metapath = outDir; + if (QFileInfo(metapath).isRelative()) + metapath = QDir::cleanPath(QDir::current().absoluteFilePath(metapath)); + qDebug() << "Generating meta data..."; + + if (!QFile::exists(metapath)) + QInstaller::mkpath(metapath); + + QDomDocument doc; + QDomElement root; + // use existing Updates.xml, if any + QFile existingUpdatesXml(QFileInfo(dataDir, QLatin1String("Updates.xml")).absoluteFilePath()); + if (!existingUpdatesXml.open(QIODevice::ReadOnly) || !doc.setContent(&existingUpdatesXml)) { + root = doc.createElement(QLatin1String("Updates")); + root.appendChild(doc.createElement(QLatin1String("ApplicationName"))).appendChild( + doc.createTextNode(appName)); + root.appendChild(doc.createElement(QLatin1String("ApplicationVersion"))).appendChild( + doc.createTextNode(appVersion)); + root.appendChild(doc.createElement(QLatin1String("Checksum"))).appendChild( + doc.createTextNode(QLatin1String("true"))); + if (!redirectUpdateUrl.isEmpty()) { + root.appendChild(doc.createElement(QLatin1String("RedirectUpdateUrl"))).appendChild( + doc.createTextNode(redirectUpdateUrl)); + } + } else { + root = doc.documentElement(); + } + + for (PackageInfoVector::const_iterator it = packages.begin(); it != packages.end(); ++it) { + const QString packageXmlPath = QString::fromLatin1("%1/meta/package.xml").arg(it->directory); + qDebug() << QString::fromLatin1("\tGenerating meta data for package %1 using %2.").arg( + it->name, packageXmlPath); + + // remove existing entry for this component from existing Updates.xml + const QDomNodeList packageNodes = root.childNodes(); + for (int i = 0; i < packageNodes.count(); ++i) { + const QDomNode node = packageNodes.at(i); + if (node.nodeName() != QLatin1String("PackageUpdate")) + continue; + if (node.firstChildElement(QLatin1String("Name")).text() != it->name) + continue; + root.removeChild(node); + --i; + } + + QDomDocument packageXml; + QFile file(packageXmlPath); + QInstaller::openForRead(&file, packageXmlPath); + QString errMsg; + int col = 0; + int line = 0; + if (!packageXml.setContent(&file, &errMsg, &line, &col)) { + throw QInstaller::Error(QObject::tr("Could not parse %1: line: %2, column: %3: %4 (%5)") + .arg(packageXmlPath, QString::number(line), QString::number(col), errMsg, it->name)); + } + const QDomNode package = packageXml.firstChildElement(QLatin1String("Package")); + + QDomElement update = doc.createElement(QLatin1String("PackageUpdate")); + + const QDomNodeList childNodes = package.childNodes(); + for (int i = 0; i < childNodes.count(); ++i) { + const QDomNode node = childNodes.at(i); + // just skip the comments... + if (node.isComment()) + continue; + const QString key = node.nodeName(); + if (key == QLatin1String("UserInterfaces")) + continue; + if (key == QLatin1String("Translations")) + continue; + if (key == QLatin1String("Licenses")) + continue; + const QString value = node.toElement().text(); + QDomElement element = doc.createElement(key); + for (int i = 0; i < node.attributes().size(); i++) { + element.setAttribute(node.attributes().item(i).toAttr().name(), + node.attributes().item(i).toAttr().value()); + } + update.appendChild(element).appendChild(doc.createTextNode(value)); + } + + // get the size of the data + quint64 componentSize = 0; + quint64 compressedComponentSize = 0; + + const QString cmpDataDir = QString::fromLatin1("%1/%2").arg(dataDir, it->name); + const QFileInfoList entries = !QDir(cmpDataDir + QLatin1String("/data")).exists() + ? QDir(cmpDataDir).entryInfoList(QDir::Files | QDir::NoDotAndDotDot) + : QDir(cmpDataDir + QLatin1String("/data")).entryInfoList(QDir::Files + | QDir::Dirs | QDir::NoDotAndDotDot); + + foreach (const QFileInfo &fi, entries) { + if (fi.isHidden()) + continue; + + try { + if (fi.isDir()) { + QDirIterator recursDirIt(fi.filePath(), QDirIterator::Subdirectories); + while (recursDirIt.hasNext()) { + componentSize += QFile(recursDirIt.next()).size(); + compressedComponentSize += QFile(recursDirIt.next()).size(); + } + } else if (Lib7z::isSupportedArchive(fi.filePath())) { + // if it's an archive already, list its files and sum the uncompressed sizes + QFile archive(fi.filePath()); + compressedComponentSize += archive.size(); + archive.open(QIODevice::ReadOnly); + const QVector< Lib7z::File > files = Lib7z::listArchive(&archive); + for (QVector< Lib7z::File >::const_iterator fileIt = files.begin(); + fileIt != files.end(); ++fileIt) { + componentSize += fileIt->uncompressedSize; + } + } else { + // otherwise just add its size + componentSize += fi.size(); + compressedComponentSize += fi.size(); + } + } catch(...) { + // ignore, that's just about the sizes - and size doesn't matter, you know? + } + } + + // add fake update files + const QStringList platforms = QStringList() << QLatin1String("Windows") << QLatin1String("MacOSX") + << QLatin1String("Linux"); + foreach (const QString &platform, platforms) { + QDomElement file = doc.createElement(QLatin1String("UpdateFile")); + file.setAttribute(QLatin1String("OS"), platform); + file.setAttribute(QLatin1String("UncompressedSize"), componentSize); + file.setAttribute(QLatin1String("CompressedSize"), compressedComponentSize); + file.appendChild(doc.createTextNode(QLatin1String("(null)"))); + update.appendChild(file); + } + + root.appendChild(update); + + if (!QDir(metapath).mkpath(it->name)) + throw QInstaller::Error(QObject::tr("Could not create directory %1.").arg(it->name)); + + // copy scripts + const QString script = package.firstChildElement(QLatin1String("Script")).text(); + if (!script.isEmpty()) { + + QFile scriptFile(script); + QString scriptContent; + if (scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream in(&scriptFile); + scriptContent = in.readAll(); + } + + // added the xml tag RequiresAdminRights to the xml if somewhere addElevatedOperation is used + if (scriptContent.contains(QLatin1String("addElevatedOperation"))) { + QDomElement requiresAdminRightsElement = + doc.createElement(QLatin1String("RequiresAdminRights")); + requiresAdminRightsElement.appendChild(doc.createTextNode(QLatin1String("true"))); + } + + qDebug() << "\tCopying associated script" << script << "into the meta package..."; + QString fromLocation(QString::fromLatin1("%1/meta/%2").arg(it->directory, script)); + QString toLocation(QString::fromLatin1("%1/%2/%3").arg(metapath, it->name, script)); + if (!QFile::copy(fromLocation, toLocation)) { + qDebug() << "failed!"; + throw QInstaller::Error(QObject::tr("Could not copy the script %1 to its target location %2.") + .arg(fromLocation, toLocation)); + } else { + qDebug() << "\tdone."; + } + } + + // copy user interfaces + const QDomNodeList uiNodes = package.firstChildElement(QLatin1String("UserInterfaces")).childNodes(); + QStringList userinterfaces; + for (int i = 0; i < uiNodes.count(); ++i) { + const QDomNode node = uiNodes.at(i); + if (node.nodeName() != QLatin1String("UserInterface")) + continue; + + const QDir dir(QString::fromLatin1("%1/meta").arg(it->directory)); + const QStringList uis = dir.entryList(QStringList(node.toElement().text()), QDir::Files); + if (uis.isEmpty()) { + throw QInstaller::Error(QObject::tr("Couldn't find any user interface matching %1 while " + "copying user interfaces of %2.").arg(node.toElement().text(), it->name)); + } + + for (QStringList::const_iterator ui = uis.begin(); ui != uis.end(); ++ui) { + qDebug() << "\tCopying associated user interface " << *ui << " into the meta " + "package..."; + userinterfaces.push_back(*ui); + if (!QFile::copy(QString::fromLatin1("%1/meta/%2").arg(it->directory, *ui), + QString::fromLatin1("%1/%2/%3").arg(metapath, it->name, *ui))) { + qDebug() << "failed!"; + throw QInstaller::Error(QObject::tr("Could not copy the UI file %1 to its target " + "location %2.").arg(*ui, it->name)); + } else { + qDebug() << "done"; + } + } + } + + if (!userinterfaces.isEmpty()) { + update.appendChild(doc.createElement(QLatin1String("UserInterfaces"))) + .appendChild(doc.createTextNode(userinterfaces.join(QChar::fromLatin1(',')))); + } + + // copy translations + const QDomNodeList qmNodes = package.firstChildElement(QLatin1String("Translations")).childNodes(); + QStringList translations; + if (!qApp->arguments().contains(QString::fromLatin1("--ignore-translations"))) { + for (int i = 0; i < qmNodes.count(); ++i) { + const QDomNode node = qmNodes.at(i); + if (node.nodeName() != QLatin1String("Translation")) + continue; + + const QDir dir(QString::fromLatin1("%1/meta").arg(it->directory)); + const QStringList qms = dir.entryList(QStringList(node.toElement().text()), QDir::Files); + if (qms.isEmpty()) { + throw QInstaller::Error(QObject::tr("Could not find any translation file matching %1 " + "while copying translations of %2.").arg(node.toElement().text(), it->name)); + } + + for (QStringList::const_iterator qm = qms.begin(); qm != qms.end(); ++qm) { + qDebug() << "\tCopying associated translation " << *qm << " into the meta " + "package..."; + translations.push_back(*qm); + if (!QFile::copy(QString::fromLatin1("%1/meta/%2").arg(it->directory, *qm), + QString::fromLatin1("%1/%2/%3").arg(metapath, it->name, *qm))) { + qDebug() << "failed!"; + throw QInstaller::Error(QObject::tr("Could not copy the translation %1 to its " + "target location %2.").arg(*qm, it->name)); + } else { + qDebug() << "done"; + } + } + } + + if (!translations.isEmpty()) { + update.appendChild(doc.createElement(QLatin1String("Translations"))) + .appendChild(doc.createTextNode(translations.join(QChar::fromLatin1(',')))); + } + + } + + // copy license files + const QDomNodeList licenseNodes = package.firstChildElement(QLatin1String("Licenses")).childNodes(); + for (int i = 0; i < licenseNodes.count(); ++i) { + const QDomNode licenseNode = licenseNodes.at(i); + if (licenseNode.nodeName() == QLatin1String("License")) { + const QString &licenseFile = + licenseNode.toElement().attributeNode(QLatin1String("file")).value(); + const QString &sourceFile = + QString::fromLatin1("%1/meta/%2").arg(it->directory).arg(licenseFile); + if (!QFile::exists(sourceFile)) { + throw QInstaller::Error(QObject::tr("Could not find any license matching %1 while " + "copying license files of %2.").arg(licenseFile, it->name)); + } + + qDebug() << "\tCopying associated license file " << licenseFile << " into " + "the meta package..."; + if (!QFile::copy(sourceFile, QString::fromLatin1("%1/%2/%3") + .arg(metapath, it->name, licenseFile))) { + qDebug() << "failed!"; + throw QInstaller::Error(QObject::tr("Could not copy the license file %1 to its " + "target location %2.").arg(licenseFile, it->name)); + } else { + qDebug() << "done."; + } + + // Translated License files + for (int j = 0; j < translations.size(); ++j) { + QFileInfo translationFile(translations.at(j)); + QFileInfo untranslated(licenseFile); + const QString &translatedLicenseFile = + QString::fromLatin1("%2_%3.%4").arg(untranslated.baseName(), + translationFile.baseName(), untranslated.completeSuffix()); + const QString &translatedSourceFile = + QString::fromLatin1("%1/meta/%2").arg(it->directory).arg(translatedLicenseFile); + if (!QFile::exists(translatedSourceFile)) { + qDebug() << "Could not find translated license file" << translatedSourceFile; + continue; + } + + qDebug() << "\tCopying associated license file" << translatedLicenseFile + << "into the meta package..."; + + if (!QFile::copy(translatedSourceFile, QString::fromLatin1("%1/%2/%3") + .arg(metapath, it->name, translatedLicenseFile))) { + qDebug() << "\tfailed!"; + } else { + qDebug() << "\tdone."; + } + } + } + } + + if (licenseNodes.count() > 0) + update.appendChild(package.firstChildElement(QLatin1String("Licenses")).cloneNode()); + } + + doc.appendChild(root); + + const QString updatesXmlFile = QFileInfo(metapath, QLatin1String("Updates.xml")).absoluteFilePath(); + QFile updatesXml(updatesXmlFile); + + QInstaller::openForWrite(&updatesXml, updatesXmlFile); + QInstaller::blockingWrite(&updatesXml, doc.toByteArray()); +} + +PackageInfoVector QInstallerTools::createListOfPackages(const QString &packagesDirectory, + const QStringList &filteredPackages, FilterType filterType) +{ + qDebug() << "Collecting information about available packages..."; + + bool ignoreInvalidPackages = qApp->arguments().contains(QString::fromLatin1("--ignore-invalid-packages")); + + PackageInfoVector dict; + const QFileInfoList entries = QDir(packagesDirectory) + .entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (QFileInfoList::const_iterator it = entries.begin(); it != entries.end(); ++it) { + if (filterType == Exclude) { + if (filteredPackages.contains(it->fileName())) + continue; + } else { + if (!filteredPackages.contains(it->fileName())) + continue; + } + qDebug() << QString::fromLatin1("\tfound subdirectory %1").arg(it->fileName()); + // because the filter is QDir::Dirs - filename means the name of the subdirectory + if (it->fileName().contains(QLatin1Char('-'))) { + if (ignoreInvalidPackages) + continue; + throw QInstaller::Error(QObject::tr("Component %1 can't contain '-'. This is not allowed, because " + "it is used as the separator between the component name and the version number internally.") + .arg(it->fileName())); + } + + QFile file(QString::fromLatin1("%1/meta/package.xml").arg(it->filePath())); + if (!file.exists()) { + if (ignoreInvalidPackages) + continue; + throw QInstaller::Error(QObject::tr("Component %1 does not contain a package " + "description(meta/package.xml is missing).").arg(it->fileName())); + } + + file.open(QIODevice::ReadOnly); + + QDomDocument doc; + QString error; + int errorLine = 0; + int errorColumn = 0; + if (!doc.setContent(&file, &error, &errorLine, &errorColumn)) { + if (ignoreInvalidPackages) + continue; + throw QInstaller::Error(QObject::tr("Component package description for %1 is invalid. " + "Error at line: %2, column: %3 -> %4").arg(it->fileName(), QString::number(errorLine), + QString::number(errorColumn), error)); + } + + const QString name = doc.firstChildElement(QLatin1String("Package")) + .firstChildElement(QLatin1String("Name")).text(); + if (name != it->fileName()) { + if (ignoreInvalidPackages) + continue; + throw QInstaller::Error(QObject::tr("Component folder name must match component name: " + "%1 in %2/").arg(name, it->fileName())); + } + + PackageInfo info; + info.name = name; + info.version = doc.firstChildElement(QLatin1String("Package")). + firstChildElement(QLatin1String("Version")).text(); + if (!QRegExp(QLatin1String("[0-9]+((\\.|-)[0-9]+)*")).exactMatch(info.version)) { + if (ignoreInvalidPackages) + continue; + throw QInstaller::Error(QObject::tr("Component version for %1 is invalid! <Version>%2</version>") + .arg(it->fileName(), info.version)); + } + info.dependencies = doc.firstChildElement(QLatin1String("Package")). + firstChildElement(QLatin1String("Dependencies")).text().split(QRegExp(QLatin1String("\\b(,|, )\\b")), + QString::SkipEmptyParts); + info.directory = it->filePath(); + dict.push_back(info); + + qDebug() << QString::fromLatin1("\t- it provides the package %1 - %2").arg(name, info.version); + } + + if (dict.isEmpty()) + qDebug() << "No available packages found at the specified location."; + + return dict; +} + +QMap<QString, QString> QInstallerTools::buildPathToVersionMap(const PackageInfoVector &info) +{ + QMap<QString, QString> map; + foreach (const PackageInfo &inf, info) + map[inf.name] = inf.version; + return map; +} + +static void writeSHA1ToNodeWithName(QDomDocument &doc, QDomNodeList &list, const QByteArray &sha1sum, + const QString &nodename) +{ + qDebug() << "searching sha1sum node for" << nodename; + for (int i = 0; i < list.size(); ++i) { + QDomNode curNode = list.at(i); + QDomNode nameTag = curNode.firstChildElement(QLatin1String("Name")); + if (!nameTag.isNull() && nameTag.toElement().text() == nodename) { + QDomNode sha1Node = doc.createElement(QLatin1String("SHA1")); + sha1Node.appendChild(doc.createTextNode(QString::fromLatin1(sha1sum.toHex().constData()))); + curNode.appendChild(sha1Node); + } + } +} + +void QInstallerTools::compressMetaDirectories(const QString &repoDir, const QString &baseDir, + const QMap<QString, QString> &versionMapping) +{ + QDomDocument doc; + QDomElement root; + // use existing Updates.xml, if any + QFile existingUpdatesXml(QFileInfo(QDir(repoDir), QLatin1String("Updates.xml")).absoluteFilePath()); + if (!existingUpdatesXml.open(QIODevice::ReadOnly) || !doc.setContent(&existingUpdatesXml)) { + qDebug() << "Could not find Updates.xml"; + } else { + root = doc.documentElement(); + } + existingUpdatesXml.close(); + + QDir dir(repoDir); + const QStringList sub = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + QDomNodeList elements = doc.elementsByTagName(QLatin1String("PackageUpdate")); + foreach (const QString &i, sub) { + QDir sd(dir); + sd.cd(i); + const QString path = QString(i).remove(baseDir); + const QString versionPrefix = versionMapping[path]; + if (path.isNull()) + continue; + const QString absPath = sd.absolutePath(); + const QString fn = QLatin1String(versionPrefix.toLatin1() + "meta.7z"); + const QString tmpTarget = repoDir + QLatin1String("/") +fn; + compressDirectory(QStringList() << absPath, tmpTarget); + + // remove the files that got compressed + QInstaller::removeFiles(absPath, true); + + QFile tmp(tmpTarget); + tmp.open(QFile::ReadOnly); + QByteArray fileToCheck = tmp.readAll(); + QByteArray sha1Sum = QCryptographicHash::hash(fileToCheck, QCryptographicHash::Sha1); + writeSHA1ToNodeWithName(doc, elements, sha1Sum, path); + const QString finalTarget = absPath + QLatin1String("/") + fn; + if (!tmp.rename(finalTarget)) + throw QInstaller::Error(QObject::tr("Could not move %1 to %2").arg(tmpTarget, finalTarget)); + } + + QInstaller::openForWrite(&existingUpdatesXml, existingUpdatesXml.fileName()); + QInstaller::blockingWrite(&existingUpdatesXml, doc.toByteArray()); + existingUpdatesXml.close(); +} + +void QInstallerTools::copyComponentData(const QString &packageDir, const QString &repoDir, + PackageInfoVector &infos) +{ + for (int i = 0; i < infos.count(); ++i) { + const PackageInfo info = infos.at(i); + const QString name = info.name; + qDebug() << "Copying component data for" << name; + const QString dataDirPath = QString::fromLatin1("%1/%2/data").arg(packageDir, name); + const QDir dataDir(dataDirPath); + if (!QDir().mkpath(QString::fromLatin1("%1/%2").arg(repoDir, name))) { + throw QInstaller::Error(QObject::tr("Could not create repository folder for component %1") + .arg(name)); + } + + const QStringList entries = dataDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files); + foreach (const QString &entry, entries) { + QString target; + QFileInfo fileInfo(dataDir.absoluteFilePath(entry)); + if (fileInfo.isFile()) { + target = QString::fromLatin1("%1/%2/%4%3").arg(repoDir, name, entry, info.version); + QFile tmp(dataDir.absoluteFilePath(entry)); + qDebug() << QString::fromLatin1("Copying archive from %1 to %2").arg(tmp.fileName(), target); + QInstaller::openForRead(&tmp, tmp.fileName()); + if (!tmp.copy(target)) { + throw QInstaller::Error(QObject::tr("Could not copy %1 to %2: %3").arg(tmp.fileName(), + target, tmp.errorString())); + } + } else if (fileInfo.isDir()) { + qDebug() << "Compressing data directory" << entry; + target = QString::fromLatin1("%1/%2/%4%3.7z").arg(repoDir, name, entry, info.version); + QInstallerTools::compressDirectory(QStringList() << dataDir.absoluteFilePath(entry), target); + } else { + continue; + } + infos[i].copiedArchives.append(target); + + QFile archiveFile(target); + QFile archiveHashFile(archiveFile.fileName() + QLatin1String(".sha1")); + + qDebug() << "Hash is stored in" << archiveHashFile.fileName(); + qDebug() << "Creating hash of archive" << archiveFile.fileName(); + + try { + QInstaller::openForRead(&archiveFile, archiveFile.fileName()); + const QByteArray archiveData = archiveFile.readAll(); + archiveFile.close(); + + QInstaller::openForWrite(&archiveHashFile, archiveHashFile.fileName()); + const QByteArray hashOfArchiveData = QCryptographicHash::hash(archiveData, + QCryptographicHash::Sha1).toHex(); + archiveHashFile.write(hashOfArchiveData); + qDebug() << "Generated sha1 hash:" << hashOfArchiveData; + infos[i].copiedArchives.append(archiveHashFile.fileName()); + archiveHashFile.close(); + } catch (const QInstaller::Error &/*e*/) { + archiveFile.close(); + archiveHashFile.close(); + throw; + } + } + } +} diff --git a/tools/common/repositorygen.h b/tools/common/repositorygen.h new file mode 100644 index 000000000..faa7a8083 --- /dev/null +++ b/tools/common/repositorygen.h @@ -0,0 +1,77 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QINSTALLER_REPOSITORYGEN_H +#define QINSTALLER_REPOSITORYGEN_H + +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QVector> + +namespace QInstallerTools { + +void printRepositoryGenOptions(); + +struct PackageInfo +{ + QString name; + QString version; + QString directory; + QStringList dependencies; + QStringList copiedArchives; +}; +typedef QVector<PackageInfo> PackageInfoVector; + +enum FilterType { + Include, + Exclude +}; + +QMap<QString, QString> buildPathToVersionMap(const PackageInfoVector &info); + +void compressMetaDirectories(const QString &repoDir); +void compressDirectory(const QStringList &paths, const QString &archivePath); +void compressMetaDirectories(const QString &repoDir, const QString &baseDir, + const QMap<QString, QString> &versionMapping); + +void copyComponentData(const QString &packageDir, const QString &repoDir, PackageInfoVector &infos); + +void generateMetaDataDirectory(const QString &outDir, const QString &dataDir, + const PackageInfoVector &packages, const QString &appName, + const QString& appVersion, const QString &redirectUpdateUrl = QString()); + +PackageInfoVector createListOfPackages(const QString &packagesDirectory, const QStringList &filteredPackages, + FilterType ftype); + +} // namespace QInstallerTools + +#endif // QINSTALLER_REPOSITORYGEN_H diff --git a/tools/extractbinarydata/extractbinarydata.pro b/tools/extractbinarydata/extractbinarydata.pro index c821a857d..056975fce 100644 --- a/tools/extractbinarydata/extractbinarydata.pro +++ b/tools/extractbinarydata/extractbinarydata.pro @@ -1,17 +1,15 @@ TEMPLATE = app DEPENDPATH += . .. INCLUDEPATH += . .. +TARGET = extractbinarydata -DESTDIR = ../../installerbuilder/bin +include(../../installerfw.pri) + +QT -= gui +LIBS += -linstaller CONFIG += console CONFIG -= app_bundle +DESTDIR = $$IFW_APP_PATH -include(../../installerbuilder/libinstaller/libinstaller.pri) - -# Input SOURCES += main.cpp - -HEADERS += - -LIBS = -L../../installerbuilder/lib -linstaller $$LIBS diff --git a/tools/maddehelper/maddehelper.pro b/tools/maddehelper/maddehelper.pro index df9954eba..bc05bb32e 100644 --- a/tools/maddehelper/maddehelper.pro +++ b/tools/maddehelper/maddehelper.pro @@ -1,12 +1,14 @@ -QT += core - -QT -= gui - +TEMPLATE = app +DEPENDPATH += . .. +INCLUDEPATH += . .. TARGET = maddehelper -CONFIG += console -CONFIG -= app_bundle -TEMPLATE = app +include(../../installerfw.pri) + +QT -= gui +CONFIG += console +CONFIG -= app_bundle +DESTDIR = $$IFW_APP_PATH SOURCES += main.cpp diff --git a/tools/repocompare/repocompare.pro b/tools/repocompare/repocompare.pro index 3e576322a..e8672634b 100644 --- a/tools/repocompare/repocompare.pro +++ b/tools/repocompare/repocompare.pro @@ -1,20 +1,18 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2011-04-04T09:43:46 -# -#------------------------------------------------- - -QT += core gui network - -TARGET = repocompare TEMPLATE = app +DEPENDPATH += . .. +INCLUDEPATH += . .. +TARGET = repocompare + +include(../../installerfw.pri) +QT += network +DESTDIR = $$IFW_APP_PATH SOURCES += main.cpp\ mainwindow.cpp \ repositorymanager.cpp -HEADERS += mainwindow.h \ +HEADERS += mainwindow.h \ repositorymanager.h -FORMS += mainwindow.ui +FORMS += mainwindow.ui diff --git a/tools/repogen/repogen.cpp b/tools/repogen/repogen.cpp new file mode 100644 index 000000000..c38018e6d --- /dev/null +++ b/tools/repogen/repogen.cpp @@ -0,0 +1,221 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "common/repositorygen.h" + +#include <errors.h> +#include <fileutils.h> +#include <init.h> +#include <settings.h> +#include <utils.h> +#include <lib7z_facade.h> + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> + +#include <iostream> + +using namespace Lib7z; +using namespace QInstaller; + +static void printUsage() +{ + const QString appName = QFileInfo(QCoreApplication::applicationFilePath()).fileName(); + std::cout << "Usage: " << appName << " [options] repository-dir" << std::endl; + std::cout << std::endl; + std::cout << "Options:" << std::endl; + + QInstallerTools::printRepositoryGenOptions(); + + std::cout << " -u|--updateurl url instructs clients to receive updates from a " << std::endl; + std::cout << " different location" << std::endl; + + std::cout << " --update Update a set of existing components (defined by --include " + << std::endl; + std::cout << " or --exclude) in the repository" << std::endl; + + std::cout << " -v|--verbose Verbose output" << std::endl; + + std::cout << std::endl; + std::cout << "Example:" << std::endl; + std::cout << " " << appName << " -p ../examples/packages -c ../examples/config -u " + "http://www.some-server.com:8080 repository/ com.nokia.sdk" << std::endl; +} + +static int printErrorAndUsageAndExit(const QString &err) +{ + std::cerr << qPrintable(err) << std::endl << std::endl; + printUsage(); + return 1; +} + +static QString makeAbsolute(const QString &path) +{ + QFileInfo fi(path); + if (fi.isAbsolute()) + return path; + return QDir::current().absoluteFilePath(path); +} + +int main(int argc, char** argv) +{ + try { + QCoreApplication app(argc, argv); + + QInstaller::init(); + + QStringList args = app.arguments().mid(1); + + QStringList filteredPackages; + bool updateExistingRepository = false; + QString packagesDir; + QString configDir; + QString redirectUpdateUrl; + QInstallerTools::FilterType filterType = QInstallerTools::Exclude; + + //TODO: use a for loop without removing values from args like it is in binarycreator.cpp + //for (QStringList::const_iterator it = args.begin(); it != args.end(); ++it) { + while (!args.isEmpty() && args.first().startsWith(QLatin1Char('-'))) { + if (args.first() == QLatin1String("--verbose") || args.first() == QLatin1String("-v")) { + args.removeFirst(); + setVerbose(true); + } else if (args.first() == QLatin1String("--exclude") || args.first() == QLatin1String("-e")) { + args.removeFirst(); + if (!filteredPackages.isEmpty()) + return printErrorAndUsageAndExit(QObject::tr("Error: --include and --exclude are mutually " + "exclusive. Use either one or the other.")); + if (args.isEmpty() || args.first().startsWith(QLatin1Char('-'))) + return printErrorAndUsageAndExit(QObject::tr("Error: Package to exclude missing")); + filteredPackages = args.first().split(QLatin1Char(',')); + args.removeFirst(); + } else if (args.first() == QLatin1String("--include") || args.first() == QLatin1String("-i")) { + args.removeFirst(); + if (!filteredPackages.isEmpty()) + return printErrorAndUsageAndExit(QObject::tr("Error: --include and --exclude are mutual " + "exclusive options. Use either one or the other.")); + if (args.isEmpty() || args.first().startsWith(QLatin1Char('-'))) + return printErrorAndUsageAndExit(QObject::tr("Error: Package to include missing")); + filteredPackages = args.first().split(QLatin1Char(',')); + args.removeFirst(); + filterType = QInstallerTools::Include; + } else if (args.first() == QLatin1String("--single") || args.first() == QLatin1String("--update")) { + args.removeFirst(); + updateExistingRepository = true; + } else if (args.first() == QLatin1String("-p") || args.first() == QLatin1String("--packages")) { + args.removeFirst(); + if (args.isEmpty()) { + return printErrorAndUsageAndExit(QObject::tr("Error: Packages parameter missing " + "argument")); + } + if (!QFileInfo(args.first()).exists()) { + return printErrorAndUsageAndExit(QObject::tr("Error: Package directory not found " + "at the specified location")); + } + packagesDir = args.first(); + args.removeFirst(); + } else if (args.first() == QLatin1String("-c") || args.first() == QLatin1String("--config")) { + args.removeFirst(); + if (args.isEmpty()) + return printErrorAndUsageAndExit(QObject::tr("Error: Config parameter missing argument")); + const QFileInfo fi(args.first()); + if (!fi.exists()) { + return printErrorAndUsageAndExit(QObject::tr("Error: Config directory %1 not found " + "at the specified location").arg(args.first())); + } + if (!fi.isDir()) { + return printErrorAndUsageAndExit(QObject::tr("Error: Configuration %1 is not a " + "directory").arg(args.first())); + } + if (!fi.isReadable()) { + return printErrorAndUsageAndExit(QObject::tr("Error: Config directory %1 is not " + "readable").arg(args.first())); + } + configDir = args.first(); + args.removeFirst(); + } else if (args.first() == QLatin1String("-u") || args.first() == QLatin1String("--updateurl")) { + args.removeFirst(); + if (args.isEmpty()) + return printErrorAndUsageAndExit(QObject::tr("Error: Config parameter missing argument")); + redirectUpdateUrl = args.first(); + args.removeFirst(); + } else if (args.first() == QLatin1String("--ignore-translations") + || args.first() == QLatin1String("--ignore-invalid-packages")) { + args.removeFirst(); + } else { + printUsage(); + return 1; + } + } + + if ((packagesDir.isEmpty() || configDir.isEmpty() || args.count() != 1)) { + printUsage(); + return 1; + } + + const QString repositoryDir = makeAbsolute(args.first()); + + if (!updateExistingRepository && QFile::exists(repositoryDir)) { + throw QInstaller::Error(QObject::tr("Repository target folder %1 already exists!") + .arg(repositoryDir)); + } + + QInstallerTools::PackageInfoVector packages = QInstallerTools::createListOfPackages(packagesDir, + filteredPackages, filterType); + QMap<QString, QString> pathToVersionMapping = buildPathToVersionMap(packages); + + foreach (const QInstallerTools::PackageInfo &package, packages) { + const QFileInfo fi(repositoryDir, package.name); + if (fi.exists()) + removeDirectory(fi.absoluteFilePath()); + } + + copyComponentData(packagesDir, repositoryDir, packages); + + TempDirDeleter tmpDeleter; + const QString metaTmp = createTemporaryDirectory(); + tmpDeleter.add(metaTmp); + + const Settings &settings = Settings::fromFileAndPrefix(configDir + QLatin1String("/config.xml"), + configDir); + generateMetaDataDirectory(metaTmp, repositoryDir, packages, settings.applicationName(), + settings.applicationVersion(), redirectUpdateUrl); + QInstallerTools::compressMetaDirectories(metaTmp, metaTmp, pathToVersionMapping); + + QFile::remove(QFileInfo(repositoryDir, QLatin1String("Updates.xml")).absoluteFilePath()); + moveDirectoryContents(metaTmp, repositoryDir); + return 0; + } catch (const Lib7z::SevenZipException &e) { + std::cerr << e.message() << std::endl; + } catch (const QInstaller::Error &e) { + std::cerr << e.message() << std::endl; + } + return 1; +} diff --git a/tools/repogen/repogen.pro b/tools/repogen/repogen.pro new file mode 100644 index 000000000..e9652b25a --- /dev/null +++ b/tools/repogen/repogen.pro @@ -0,0 +1,17 @@ +TEMPLATE = app +TARGET = repogen +DEPENDPATH += . .. ../common +INCLUDEPATH += . .. ../common + +include(../../installerfw.pri) + +QT -= gui +LIBS += -linstaller + +CONFIG += console +CONFIG -= app_bundle +DESTDIR = $$IFW_APP_PATH + +SOURCES += repogen.cpp \ + repositorygen.cpp +HEADERS += repositorygen.h diff --git a/tools/repogenfromonlinerepo/repogenfromonlinerepo.pro b/tools/repogenfromonlinerepo/repogenfromonlinerepo.pro index f34c58f08..8b3a01124 100644 --- a/tools/repogenfromonlinerepo/repogenfromonlinerepo.pro +++ b/tools/repogenfromonlinerepo/repogenfromonlinerepo.pro @@ -6,7 +6,7 @@ TARGET = repogenfromonlinerepo include(../../installerfw.pri) QT -= gui -QT += xml network +QT += network CONFIG += console CONFIG -= app_bundle diff --git a/tools/tools.pro b/tools/tools.pro index 627a6eeaa..963d4a2da 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -1,7 +1,11 @@ CONFIG += ordered TEMPLATE = subdirs -SUBDIRS += extractbinarydata \ + +SUBDIRS += \ + archivegen \ + binarycreator \ + extractbinarydata \ repocompare \ + repogen \ repogenfromonlinerepo -win32:SUBDIRS += maddehelper - +win32:SUBDIRS += maddehelper
\ No newline at end of file |