summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArttu Tarkiainen <arttu.tarkiainen@qt.io>2020-12-01 13:00:15 +0200
committerArttu Tarkiainen <arttu.tarkiainen@qt.io>2021-01-15 09:27:07 +0200
commit20f354b38b83e4aab107b388fe3c5e2ccd878946 (patch)
tree9da138aca91abdd931eb9fb4c74ffb35dddc7377
parentf61c19eb81dc692b9c5215f5bf94f3426b0f4f86 (diff)
Add support for generating offline installer from online
Task-number: QTIFW-1945 Change-Id: Ic8a076a28385e99ad09cfbccd07c7012d6570639 Reviewed-by: Katja Marttila <katja.marttila@qt.io>
-rw-r--r--doc/installerfw-using.qdoc14
-rw-r--r--doc/installerfw.qdoc7
-rw-r--r--doc/scripting-api/packagemanagercore.qdoc28
-rw-r--r--src/libs/ifwtools/binarycreator.cpp25
-rw-r--r--src/libs/ifwtools/repositorygen.cpp11
-rw-r--r--src/libs/installer/commandlineparser.cpp7
-rw-r--r--src/libs/installer/constants.h9
-rw-r--r--src/libs/installer/fileutils.cpp95
-rw-r--r--src/libs/installer/fileutils.h5
-rw-r--r--src/libs/installer/packagemanagercore.cpp185
-rw-r--r--src/libs/installer/packagemanagercore.h14
-rw-r--r--src/libs/installer/packagemanagercore_p.cpp212
-rw-r--r--src/libs/installer/packagemanagercore_p.h10
-rw-r--r--src/libs/installer/packagemanagercoredata.cpp5
-rw-r--r--src/libs/installer/packagemanagercoredata.h5
-rw-r--r--src/libs/installer/settings.cpp2
-rw-r--r--src/sdk/commandlineinterface.cpp31
-rw-r--r--src/sdk/commandlineinterface.h1
-rw-r--r--src/sdk/main.cpp3
-rw-r--r--tests/auto/installer/cliinterface/tst_cliinterface.cpp8
20 files changed, 633 insertions, 44 deletions
diff --git a/doc/installerfw-using.qdoc b/doc/installerfw-using.qdoc
index cb0e4d3ad..d4f4b6688 100644
--- a/doc/installerfw-using.qdoc
+++ b/doc/installerfw-using.qdoc
@@ -470,6 +470,20 @@
maintenancetool.exe purge
\endcode
+ \section1 Creating Custom Offline Installers
+
+ To create a personal offline installer containing a selected set of components from online repositories,
+ use the \c create-offline command. This is a useful feature for scenarios where the same installation
+ content will be deployed multiple times or on several machines, as it saves time spent on downloading
+ component archives and enables easily reproducible installations. The \c --offline-installer-name
+ option can be used to set a desired name for the generated offline installer.
+
+ Note that you can create offline installers only from online installers with valid repositories enabled.
+
+ \code
+ installer.exe --root "C:\TargetFolder" --offline-installer-name "MyInstaller" create-offline componentA componentB
+ \endcode
+
\section1 Unattended Usage
By default, the generated installers may ask for additional information during installation,
diff --git a/doc/installerfw.qdoc b/doc/installerfw.qdoc
index ebc1e2044..ef7479a33 100644
--- a/doc/installerfw.qdoc
+++ b/doc/installerfw.qdoc
@@ -223,6 +223,10 @@
\li -t, --root <directory>
\li [CLI] Set the installation root directory.
\row
+ \li --oi, --offline-installer-name <filename>
+ \li [CLI] Set custom filename for the generated offline installer. Without this
+ the original filename is used with an added "_offline-yyyy-MM-dd" suffix.
+ \row
\li -p, --platform <plugin>
\li Use the specified platform plugin.
\row
@@ -316,6 +320,9 @@
\li se, search <regexp>
\li Search available packages. If no search pattern is given, show all available packages.
\row
+ \li co, create-offline <pkg ...>
+ \li Create offline installer from selected packages.
+ \row
\li pr, purge
\li Uninstall all packages and remove the program directory.
\endtable
diff --git a/doc/scripting-api/packagemanagercore.qdoc b/doc/scripting-api/packagemanagercore.qdoc
index 74b430d2f..8a9d297a0 100644
--- a/doc/scripting-api/packagemanagercore.qdoc
+++ b/doc/scripting-api/packagemanagercore.qdoc
@@ -211,6 +211,22 @@
*/
/*!
+ \qmlsignal installer::offlineGenerationStarted()
+
+ Triggered when offline installer generation has started.
+
+ \sa offlineGenerationFinished()
+*/
+
+/*!
+ \qmlsignal installer::offlineGenerationFinished
+
+ Triggered when offline installer generation has finished.
+
+ \sa offlineGenerationStarted()
+*/
+
+/*!
\qmlsignal installer::titleMessageChanged(string title)
Emitted when the text of the installer status (on the PerformInstallation page) changes to
@@ -832,6 +848,12 @@
*/
/*!
+ \qmlmethod boolean installer::isOfflineGenerator()
+
+ Returns \c true if the application is executed in offline generation mode.
+*/
+
+/*!
\qmlmethod boolean installer::isUserSetBinaryMarker()
Returns \c true if the magic binary marker has been set by user.
@@ -868,6 +890,12 @@
*/
/*!
+ \qmlmethod boolean installer::runOfflineGenerator()
+
+ Runs the offline generator. Returns \c true on success, \c false otherwise.
+*/
+
+/*!
\qmlmethod void installer::languageChanged()
Calls languangeChanged on all components.
diff --git a/src/libs/ifwtools/binarycreator.cpp b/src/libs/ifwtools/binarycreator.cpp
index 8fcc14ca3..deea01ed7 100644
--- a/src/libs/ifwtools/binarycreator.cpp
+++ b/src/libs/ifwtools/binarycreator.cpp
@@ -37,6 +37,7 @@
#include "repository.h"
#include "settings.h"
#include "utils.h"
+#include "fileutils.h"
#include <QDateTime>
#include <QDirIterator>
@@ -433,8 +434,13 @@ static int assemble(Input input, const QInstaller::Settings &settings, const QSt
return EXIT_FAILURE;
}
+#ifdef Q_OS_MACOS
+ // installer executable
+ chmod755(input.outputPath);
+#endif
#ifndef Q_OS_WIN
- chmod755(out.fileName());
+ // installer executable on linux, installer.dat on macOS
+ chmod755(targetName);
#endif
QFile::remove(tempFile);
@@ -580,6 +586,9 @@ void QInstallerTools::copyConfigData(const QString &configFile, const QString &t
const QString targetConfigFile = targetDir + QLatin1String("/config.xml");
QInstallerTools::copyWithException(sourceConfigFile, targetConfigFile, QLatin1String("configuration"));
+ // Permissions might be set to bogus values
+ QInstaller::setDefaultFilePermissions(targetConfigFile, DefaultFilePermissions::NonExecutable);
+
QFile configXml(targetConfigFile);
QInstaller::openForRead(&configXml);
@@ -699,13 +708,19 @@ int QInstallerTools::createBinary(BinaryCreatorArgs args, QString &argumentError
"contain any components apart from the root component.");
return EXIT_FAILURE;
}
-#ifdef Q_OS_WIN
- if (!args.templateBinary.endsWith(suffix))
- args.templateBinary = args.templateBinary + suffix;
-#endif
if (!QFileInfo(args.templateBinary).exists()) {
+#ifdef Q_OS_WIN
+ if (!args.templateBinary.endsWith(suffix))
+ args.templateBinary = args.templateBinary + suffix;
+ // Try again with added executable suffix
+ if (!QFileInfo(args.templateBinary).exists()) {
+ argumentError = QString::fromLatin1("Error: Template base binary not found at the specified location.");
+ return EXIT_FAILURE;
+ }
+#else
argumentError = QString::fromLatin1("Error: Template not found at the specified location.");
return EXIT_FAILURE;
+#endif
}
const QFileInfo fi(args.configFile);
if (!fi.exists()) {
diff --git a/src/libs/ifwtools/repositorygen.cpp b/src/libs/ifwtools/repositorygen.cpp
index e30234f52..fbcf7b9f3 100644
--- a/src/libs/ifwtools/repositorygen.cpp
+++ b/src/libs/ifwtools/repositorygen.cpp
@@ -609,12 +609,19 @@ PackageInfoVector QInstallerTools::createListOfRepositoryPackages(const QStringL
if (!hasUnifiedMetaFile) {
const QDomElement sha1 = el.firstChildElement(QInstaller::scSHA1);
if (!sha1.isNull()) {
+ // 1. First, try with normal repository structure
QString metaFile = QString::fromLatin1("%1/%3%2").arg(info.directory,
QString::fromLatin1("meta.7z"), info.version);
if (!QFileInfo(metaFile).exists()) {
- throw QInstaller::Error(QString::fromLatin1("Could not find meta archive for component "
- "%1 %2 in repository %3.").arg(info.name, info.version, it->filePath()));
+ // 2. If that does not work, check for fetched temporary repository structure
+ metaFile = QString::fromLatin1("%1/%2-%3-%4").arg(it->filePath(),
+ info.name, info.version, QString::fromLatin1("meta.7z"));
+
+ if (!QFileInfo(metaFile).exists()) {
+ throw QInstaller::Error(QString::fromLatin1("Could not find meta archive for component "
+ "%1 %2 in repository %3.").arg(info.name, info.version, it->filePath()));
+ }
}
info.metaFile = metaFile;
}
diff --git a/src/libs/installer/commandlineparser.cpp b/src/libs/installer/commandlineparser.cpp
index 3ffbdb9b5..9812023cb 100644
--- a/src/libs/installer/commandlineparser.cpp
+++ b/src/libs/installer/commandlineparser.cpp
@@ -59,6 +59,8 @@ CommandLineParser::CommandLineParser()
.arg(CommandLineOptions::scListShort, CommandLineOptions::scListLong)
+ indent + QString::fromLatin1("%1, %2 - search available packages - <regexp>\n")
.arg(CommandLineOptions::scSearchShort, CommandLineOptions::scSearchLong)
+ + indent + QString::fromLatin1("%1, %2 - create offline installer from selected packages - <pkg ...>\n")
+ .arg(CommandLineOptions::scCreateOfflineShort, CommandLineOptions::scCreateOfflineLong)
+ indent + QString::fromLatin1("%1, %2 - uninstall all packages and remove entire program directory")
.arg(CommandLineOptions::scPurgeShort, CommandLineOptions::scPurgeLong);
@@ -129,6 +131,11 @@ CommandLineParser::CommandLineParser()
QLatin1String("[CLI] Set installation root directory."),
QLatin1String("directory")));
m_parser.addOption(QCommandLineOption(QStringList()
+ << CommandLineOptions::scOfflineInstallerNameShort << CommandLineOptions::scOfflineInstallerNameLong,
+ QLatin1String("[CLI] Set custom filename for the generated offline installer. Without this "
+ "the original filename is used with an added \"_offline-yyyy-MM-dd\" suffix."),
+ QLatin1String("filename")));
+ m_parser.addOption(QCommandLineOption(QStringList()
<< CommandLineOptions::scPlatformShort << CommandLineOptions::scPlatformLong,
QLatin1String("Use the specified platform plugin."),
QLatin1String("plugin")));
diff --git a/src/libs/installer/constants.h b/src/libs/installer/constants.h
index 7ecdf2b89..304efb772 100644
--- a/src/libs/installer/constants.h
+++ b/src/libs/installer/constants.h
@@ -64,6 +64,7 @@ static const QLatin1String scInstalledVersion("InstalledVersion");
static const QLatin1String scUncompressedSize("UncompressedSize");
static const QLatin1String scUncompressedSizeSum("UncompressedSizeSum");
static const QLatin1String scRequiresAdminRights("RequiresAdminRights");
+static const QLatin1String scOfflineBinaryName("OfflineBinaryName");
static const QLatin1String scSHA1("SHA1");
// constants used throughout the components class
@@ -83,6 +84,8 @@ static const QLatin1String scTargetConfigurationFile("TargetConfigurationFile");
static const QLatin1String scAllowNonAsciiCharacters("AllowNonAsciiCharacters");
static const QLatin1String scDisableAuthorizationFallback("DisableAuthorizationFallback");
static const QLatin1String scDisableCommandLineInterface("DisableCommandLineInterface");
+static const QLatin1String scRemoteRepositories("RemoteRepositories");
+static const QLatin1String scRepositoryCategories("RepositoryCategories");
static const QLatin1String scRepositorySettingsPageVisible("RepositorySettingsPageVisible");
static const QLatin1String scAllowSpaceInPath("AllowSpaceInPath");
static const QLatin1String scWizardStyle("WizardStyle");
@@ -135,6 +138,8 @@ static const QLatin1String scListShort("li");
static const QLatin1String scListLong("list");
static const QLatin1String scSearchShort("se");
static const QLatin1String scSearchLong("search");
+static const QLatin1String scCreateOfflineShort("co");
+static const QLatin1String scCreateOfflineLong("create-offline");
static const QLatin1String scPurgeShort("pr");
static const QLatin1String scPurgeLong("purge");
@@ -178,6 +183,8 @@ static const QLatin1String scConfirmCommandLong("confirm-command");
// Misc installation options
static const QLatin1String scRootShort("t");
static const QLatin1String scRootLong("root");
+static const QLatin1String scOfflineInstallerNameShort("oi");
+static const QLatin1String scOfflineInstallerNameLong("offline-installer-name");
static const QLatin1String scPlatformShort("p");
static const QLatin1String scPlatformLong("platform");
static const QLatin1String scNoForceInstallationShort("nf");
@@ -221,6 +228,8 @@ static const QStringList scCommandLineInterfaceOptions = {
scListLong,
scSearchShort,
scSearchLong,
+ scCreateOfflineShort,
+ scCreateOfflineLong,
scPurgeShort,
scPurgeLong
};
diff --git a/src/libs/installer/fileutils.cpp b/src/libs/installer/fileutils.cpp
index 273901267..3c9f2ad1c 100644
--- a/src/libs/installer/fileutils.cpp
+++ b/src/libs/installer/fileutils.cpp
@@ -29,6 +29,7 @@
#include "globals.h"
#include "constants.h"
+#include "fileio.h"
#include <errors.h>
#include <QtCore/QDateTime>
@@ -724,3 +725,97 @@ void QInstaller::replaceHighDpiImage(QString &imagePath)
imagePath = highdpiPixmap;
}
}
+
+/*!
+ Copies an internal configuration file from \a source to \a target. The XML elements,
+ and their children, specified by \a elementsToRemoveTags will be removed from the \a target
+ file. All relative filenames referenced in the \a source configuration file will be
+ also copied to the location of the \a target file.
+
+ Throws \c QInstaller::Error in case of failure.
+*/
+void QInstaller::trimmedCopyConfigData(const QString &source, const QString &target, const QStringList &elementsToRemoveTags)
+{
+ qCDebug(QInstaller::lcDeveloperBuild) << "Copying configuration file and associated data.";
+
+ const QString targetPath = QFileInfo(target).absolutePath();
+ if (!QDir(targetPath).exists() && !QDir().mkpath(targetPath)) {
+ throw Error(QCoreApplication::translate("QInstaller",
+ "Cannot create directory \"%1\".").arg(targetPath));
+ }
+
+ QFile xmlFile(source);
+ if (!xmlFile.copy(target)) {
+ throw Error(QCoreApplication::translate("QInstaller",
+ "Cannot copy file \"%1\" to \"%2\": %3").arg(source, target, xmlFile.errorString()));
+ }
+ setDefaultFilePermissions(target, DefaultFilePermissions::NonExecutable);
+
+ xmlFile.setFileName(target);
+ QInstaller::openForRead(&xmlFile); // throws in case of error
+
+ QDomDocument dom;
+ dom.setContent(&xmlFile);
+ xmlFile.close();
+
+ foreach (auto elementTag, elementsToRemoveTags) {
+ QDomNodeList elementsToRemove = dom.elementsByTagName(elementTag);
+ for (int i = 0; i < elementsToRemove.length(); i++) {
+ QDomNode elementToRemove = elementsToRemove.item(i);
+ elementToRemove.parentNode().removeChild(elementToRemove);
+
+ qCDebug(QInstaller::lcDeveloperBuild) << "Removed dom element from target file:"
+ << elementToRemove.toElement().text();
+ }
+ }
+
+ const QDomNodeList children = dom.documentElement().childNodes();
+ copyConfigChildElements(dom, children, QFileInfo(source).absolutePath(), QFileInfo(target).absolutePath());
+
+ QInstaller::openForWrite(&xmlFile); // throws in case of error
+ QTextStream stream(&xmlFile);
+ dom.save(stream, 4); // use 4 as the amount of space for indentation
+
+ qCDebug(QInstaller::lcDeveloperBuild) << "Finished copying configuration data.";
+}
+
+/*!
+ \internal
+
+ Recursively iterates over a list of QDomNode \a objects belonging to \a dom and their
+ children accordingly, searching for relative file names. Found files are copied from
+ \a sourceDir to \a targetDir.
+
+ Throws \c QInstaller::Error in case of failure.
+*/
+void QInstaller::copyConfigChildElements(QDomDocument &dom, const QDomNodeList &objects,
+ const QString &sourceDir, const QString &targetDir)
+{
+ for (int i = 0; i < objects.length(); i++) {
+ QDomElement domElement = objects.at(i).toElement();
+ if (domElement.isNull())
+ continue;
+
+ // Iterate recursively over all child nodes
+ const QDomNodeList elementChildren = domElement.childNodes();
+ QInstaller::copyConfigChildElements(dom, elementChildren, sourceDir, targetDir);
+
+ // Filename may also contain a path relative to source directory but we
+ // copy it strictly into target directory without extra paths
+ const QString newName = domElement.text()
+ .replace(QRegExp(QLatin1String("\\\\|/|\\.|:")), QLatin1String("_"));
+
+ const QString targetFile = targetDir + QDir::separator() + newName;
+ const QFileInfo elementFileInfo = QFileInfo(sourceDir, domElement.text());
+
+ if (!elementFileInfo.exists() || elementFileInfo.isDir())
+ continue;
+
+ domElement.replaceChild(dom.createTextNode(newName), domElement.firstChild());
+
+ if (!QFile::copy(elementFileInfo.absoluteFilePath(), targetFile)) {
+ throw Error(QCoreApplication::translate("QInstaller",
+ "Cannot copy file \"%1\" to \"%2\".").arg(elementFileInfo.absoluteFilePath(), targetFile));
+ }
+ }
+}
diff --git a/src/libs/installer/fileutils.h b/src/libs/installer/fileutils.h
index 101a6a1cc..ac3f95098 100644
--- a/src/libs/installer/fileutils.h
+++ b/src/libs/installer/fileutils.h
@@ -33,6 +33,8 @@
#include <QtCore/QSet>
#include <QtCore/QString>
#include <QtCore/QStringList>
+#include <QtXml/QDomDocument>
+#include <QtXml/QDomNodeList>
QT_BEGIN_NAMESPACE
class QFileInfo;
@@ -94,6 +96,9 @@ private:
QString replacePath(const QString &path, const QString &pathBefore, const QString &pathAfter);
void replaceHighDpiImage(QString &imagePath);
+ void INSTALLER_EXPORT trimmedCopyConfigData(const QString &source, const QString &target, const QStringList &elementsToRemoveTags);
+ void copyConfigChildElements(QDomDocument &dom, const QDomNodeList &objects, const QString &sourceDir, const QString &targetDir);
+
#ifdef Q_OS_WIN
QString INSTALLER_EXPORT getLongPathName(const QString &name);
QString INSTALLER_EXPORT getShortPathName(const QString &name);
diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp
index 8e205b7d7..ca128c6ea 100644
--- a/src/libs/installer/packagemanagercore.cpp
+++ b/src/libs/installer/packagemanagercore.cpp
@@ -317,6 +317,18 @@ using namespace QInstaller;
*/
/*!
+ \fn QInstaller::PackageManagerCore::offlineGenerationStarted()
+
+ \sa {installer::offlineGenerationStarted()}{installer.offlineGenerationStarted()}
+*/
+
+/*!
+ \fn QInstaller::PackageManagerCore::offlineGenerationFinished()
+
+ \sa {installer::offlineGenerationFinished()}{installer.offlineGenerationFinished()}
+*/
+
+/*!
\fn QInstaller::PackageManagerCore::titleMessageChanged(const QString &title)
Emitted when the text of the installer status (on the PerformInstallation page) changes to
@@ -2249,6 +2261,47 @@ bool PackageManagerCore::componentUninstallableFromCommandLine(const QString &co
}
/*!
+ \internal
+
+ Tries to set \c Qt::CheckStateRole to \c Qt::Checked for given \a components in the
+ default component model. Returns \c true if \a components contains at least one component
+ eligible for installation, otherwise returns \c false. An error message can be retrieved
+ with \a errorMessage.
+*/
+bool PackageManagerCore::checkComponentsForInstallation(const QStringList &components, QString &errorMessage)
+{
+ bool installComponentsFound = false;
+
+ ComponentModel *model = defaultComponentModel();
+ foreach (const QString &name, components) {
+ const QModelIndex &idx = model->indexFromComponentName(name);
+ Component *component = componentByName(name);
+ if (component && idx.isValid()) {
+ if ((model->data(idx, Qt::CheckStateRole) == QVariant::Invalid) && !component->forcedInstallation()) {
+ // User cannot select the component, check why
+ if (component->autoDependencies().count() > 0) {
+ errorMessage.append(tr("Cannot install component %1. Component is installed only as automatic "
+ "dependency to %2.\n").arg(name, component->autoDependencies().join(QLatin1Char(','))));
+ } else if (!component->isCheckable()) {
+ errorMessage.append(tr("Cannot install component %1. Component is not checkable meaning you "
+ "have to select one of the subcomponents.\n").arg(name));
+ }
+ } else if (component->isInstalled()) {
+ errorMessage.append(tr("Component %1 already installed\n").arg(name));
+ } else {
+ model->setData(idx, Qt::Checked, Qt::CheckStateRole);
+ installComponentsFound = true;
+ }
+ } else { // idx is invalid and component valid when we have invisible virtual component
+ component && component->isVirtual()
+ ? errorMessage.append(tr("Cannot install %1. Component is virtual.\n").arg(name))
+ : errorMessage.append(tr("Cannot install %1. Component not found.\n").arg(name));
+ }
+ }
+ return installComponentsFound;
+}
+
+/*!
Lists installed packages without GUI. List of packages can be filtered with \a regexp.
*/
void PackageManagerCore::listInstalledPackages(const QString &regexp)
@@ -2451,6 +2504,33 @@ PackageManagerCore::Status PackageManagerCore::removeInstallationSilently()
}
/*!
+ Creates an offline installer from selected \a componentsToAdd without displaying
+ a user interface. Virtual components cannot be selected unless made visible with
+ --show-virtual-components as in installation. AutoDependOn nor non-checkable components
+ cannot be selected directly. Returns \c PackageManagerCore::Status.
+*/
+PackageManagerCore::Status PackageManagerCore::createOfflineInstaller(const QStringList &componentsToAdd)
+{
+ setOfflineGenerator();
+ // init default model before fetching remote packages tree
+ ComponentModel *model = defaultComponentModel();
+ Q_UNUSED(model);
+ if (!fetchRemotePackagesTree())
+ return status();
+
+ QString errorMessage;
+ if (checkComponentsForInstallation(componentsToAdd, errorMessage)) {
+ if (d->calculateComponentsAndRun()) {
+ qCDebug(QInstaller::lcInstallerInstallLog)
+ << "Created installer to:" << offlineBinaryName();
+ }
+ } else {
+ qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << errorMessage;
+ }
+ return status();
+}
+
+/*!
Installs the selected components \a components without displaying a user
interface. Virtual components cannot be installed unless made visible with
--show-virtual-components. AutoDependOn nor non-checkable components cannot
@@ -2475,39 +2555,18 @@ PackageManagerCore::Status PackageManagerCore::installSelectedComponentsSilently
}
}
+ // init default model before fetching remote packages tree
ComponentModel *model = defaultComponentModel();
+ Q_UNUSED(model);
if (!fetchRemotePackagesTree())
return status();
- bool installComponentsFound = false;
- foreach (const QString &name, components){
- const QModelIndex &idx = model->indexFromComponentName(name);
- Component *component = componentByName(name);
- if (idx.isValid()) {
- if ((model->data(idx, Qt::CheckStateRole) == QVariant::Invalid) &&
- !component->forcedInstallation()) { // User cannot select the component, check why
- if (component && component->autoDependencies().count() > 0)
- qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Cannot install component "<< name
- << ". Component is installed only as automatic dependency to "<< component->autoDependencies().join(QLatin1Char(',')) << ".";
- if (component && !component->isCheckable())
- qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Cannot install component "<< name
- <<". Component is not checkable meaning you have to select one of the subcomponents.";
- } else if (component->isInstalled()) {
- qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Component " << name <<" already installed";
- } else {
- model->setData(idx, Qt::Checked, Qt::CheckStateRole);
- installComponentsFound = true;
- }
- } else { // idx is invalid and component valid when we have invisible virtual component
- if (component && component->isVirtual())
- qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Cannot install " << name <<". Component is virtual.";
- else
- qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << "Cannot install " << name <<". Component not found.";
- }
- }
- if (installComponentsFound) {
+ QString errorMessage;
+ if (checkComponentsForInstallation(components, errorMessage)) {
if (d->calculateComponentsAndRun())
qCDebug(QInstaller::lcInstallerInstallLog) << "Components installed successfully";
+ } else {
+ qCDebug(QInstaller::lcInstallerInstallLog).noquote().nospace() << errorMessage;
}
return status();
}
@@ -3045,6 +3104,27 @@ void PackageManagerCore::setInstallerBaseBinary(const QString &path)
}
/*!
+ Sets the \c installerbase binary located at \a path to use when writing the
+ offline installer. Setting this makes it possible to run the offline generator
+ in cases where we are not running a real installer, i.e. when executing autotests.
+
+ For normal runs, the executable segment of the running installer will be used.
+*/
+void PackageManagerCore::setOfflineBaseBinary(const QString &path)
+{
+ d->m_offlineBaseBinaryUnreplaced = path;
+}
+
+/*!
+ Adds the resource collection in \a rcPath to the list of resource files
+ to be included into the generated offline installer binary.
+*/
+void PackageManagerCore::addResourcesForOfflineGeneration(const QString &rcPath)
+{
+ d->m_offlineGeneratorResourceCollections.append(rcPath);
+}
+
+/*!
Returns the installer value for \a key. If \a key is not known to the system, \a defaultValue is
returned. Additionally, on Windows, \a key can be a registry key.
@@ -3199,6 +3279,22 @@ QString PackageManagerCore::installerBinaryPath() const
}
/*!
+ Sets the \a name for the generated offline binary.
+*/
+void PackageManagerCore::setOfflineBinaryName(const QString &name)
+{
+ setValue(scOfflineBinaryName, name);
+}
+
+/*!
+ Returns the path set for the generated offline binary.
+*/
+QString PackageManagerCore::offlineBinaryName() const
+{
+ return d->offlineBinaryName();
+}
+
+/*!
\sa {installer::setInstaller}{installer.setInstaller}
\sa isInstaller(), setUpdater(), setPackageManager()
*/
@@ -3293,6 +3389,24 @@ bool PackageManagerCore::isPackageManager() const
}
/*!
+ Sets current installer to be offline generator based on \a offlineGenerator.
+*/
+void PackageManagerCore::setOfflineGenerator(bool offlineGenerator)
+{
+ d->m_offlineGenerator = offlineGenerator;
+}
+
+/*!
+ Returns \c true if current installer is executed as offline generator.
+
+ \sa {installer::isOfflineGenerator}{installer.isOfflineGenerator}
+*/
+bool PackageManagerCore::isOfflineGenerator() const
+{
+ return d->isOfflineGenerator();
+}
+
+/*!
Sets the installer magic binary marker based on \a magicMarker and
userSetBinaryMarker to \c true.
*/
@@ -3380,6 +3494,16 @@ bool PackageManagerCore::runPackageUpdater()
}
/*!
+ Runs the offline generator. Returns \c true on success, \c false otherwise.
+
+ \sa {installer::runOfflineGenerator}{installer.runOfflineGenerator}
+*/
+bool PackageManagerCore::runOfflineGenerator()
+{
+ return d->runOfflineGenerator();
+}
+
+/*!
\sa {installer::languageChanged}{installer.languageChanged}
*/
void PackageManagerCore::languageChanged()
@@ -3389,17 +3513,20 @@ void PackageManagerCore::languageChanged()
}
/*!
- Runs the installer, uninstaller, updater, or package manager, depending on
+ Runs the installer, uninstaller, updater, package manager, or offline generator depending on
the type of this binary. Returns \c true on success, otherwise \c false.
*/
bool PackageManagerCore::run()
{
- if (isInstaller())
+ if (isOfflineGenerator())
+ return d->runOfflineGenerator();
+ else if (isInstaller())
return d->runInstaller();
else if (isUninstaller())
return d->runUninstaller();
else if (isMaintainer())
return d->runPackageUpdater();
+
return false;
}
diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h
index 5e6ab2867..70ec83f15 100644
--- a/src/libs/installer/packagemanagercore.h
+++ b/src/libs/installer/packagemanagercore.h
@@ -160,6 +160,9 @@ public:
Q_INVOKABLE static QString findPath(const QString &name, const QStringList &paths = QStringList());
Q_INVOKABLE void setInstallerBaseBinary(const QString &path);
+ void setOfflineBaseBinary(const QString &path);
+
+ void addResourcesForOfflineGeneration(const QString &rcPath);
// parameter handling
Q_INVOKABLE bool containsValue(const QString &key) const;
@@ -179,6 +182,9 @@ public:
QString maintenanceToolName() const;
QString installerBinaryPath() const;
+ void setOfflineBinaryName(const QString &name);
+ QString offlineBinaryName() const;
+
bool testChecksum() const;
void setTestChecksum(bool test);
@@ -241,6 +247,7 @@ public:
PackageManagerCore::Status installDefaultComponentsSilently();
PackageManagerCore::Status uninstallComponentsSilently(const QStringList& components);
PackageManagerCore::Status removeInstallationSilently();
+ PackageManagerCore::Status createOfflineInstaller(const QStringList &componentsToAdd);
// convenience
Q_INVOKABLE void setInstaller();
@@ -256,6 +263,9 @@ public:
Q_INVOKABLE void setPackageManager();
Q_INVOKABLE bool isPackageManager() const;
+ void setOfflineGenerator(bool offlineGenerator = true);
+ Q_INVOKABLE bool isOfflineGenerator() const;
+
void setUserSetBinaryMarker(qint64 magicMarker);
Q_INVOKABLE bool isUserSetBinaryMarker() const;
@@ -323,6 +333,7 @@ public Q_SLOTS:
bool runInstaller();
bool runUninstaller();
bool runPackageUpdater();
+ bool runOfflineGenerator();
void interrupt();
void setCanceled();
void languageChanged();
@@ -360,6 +371,8 @@ Q_SIGNALS:
void updateFinished();
void uninstallationStarted();
void uninstallationFinished();
+ void offlineGenerationStarted();
+ void offlineGenerationFinished();
void titleMessageChanged(const QString &title);
void wizardPageInsertionRequested(QWidget *widget, QInstaller::PackageManagerCore::WizardPage page);
@@ -399,6 +412,7 @@ private:
bool fetchPackagesTree(const PackagesList &packages, const LocalPackagesHash installedPackages);
bool componentUninstallableFromCommandLine(const QString &componentName);
+ bool checkComponentsForInstallation(const QStringList &components, QString &errorMessage);
private:
PackageManagerCorePrivate *const d;
diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp
index b3c543758..e3dfe7f4f 100644
--- a/src/libs/installer/packagemanagercore_p.cpp
+++ b/src/libs/installer/packagemanagercore_p.cpp
@@ -48,6 +48,7 @@
#include "uninstallercalculator.h"
#include "componentchecker.h"
#include "globals.h"
+#include "binarycreator.h"
#include "selfrestarter.h"
#include "filedownloaderfactory.h"
@@ -238,6 +239,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core)
, m_autoAcceptLicenses(false)
, m_disableWriteMaintenanceTool(false)
, m_autoConfirmCommand(false)
+ , m_offlineGenerator(false)
{
}
@@ -276,6 +278,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q
, m_autoAcceptLicenses(false)
, m_disableWriteMaintenanceTool(false)
, m_autoConfirmCommand(false)
+ , m_offlineGenerator(false)
{
foreach (const OperationBlob &operation, performedOperations) {
QScopedPointer<QInstaller::Operation> op(KDUpdater::UpdateOperationFactory::instance()
@@ -302,6 +305,10 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q
m_core, &PackageManagerCore::uninstallationStarted);
connect(this, &PackageManagerCorePrivate::uninstallationFinished,
m_core, &PackageManagerCore::uninstallationFinished);
+ connect(this, &PackageManagerCorePrivate::offlineGenerationStarted,
+ m_core, &PackageManagerCore::offlineGenerationStarted);
+ connect(this, &PackageManagerCorePrivate::offlineGenerationFinished,
+ m_core, &PackageManagerCore::offlineGenerationFinished);
}
PackageManagerCorePrivate::~PackageManagerCorePrivate()
@@ -638,6 +645,10 @@ void PackageManagerCorePrivate::initialize(const QHash<QString, QString> &params
ProgressCoordinator::instance(), &ProgressCoordinator::reset);
connect(this, &PackageManagerCorePrivate::uninstallationStarted,
ProgressCoordinator::instance(), &ProgressCoordinator::reset);
+ disconnect(this, &PackageManagerCorePrivate::offlineGenerationStarted,
+ ProgressCoordinator::instance(), &ProgressCoordinator::reset);
+ connect(this, &PackageManagerCorePrivate::offlineGenerationStarted,
+ ProgressCoordinator::instance(), &ProgressCoordinator::reset);
if (!isInstaller())
m_localPackageHub->setFileName(componentsXmlPath());
@@ -696,6 +707,11 @@ bool PackageManagerCorePrivate::isPackageManager() const
return m_magicBinaryMarker == BinaryContent::MagicPackageManagerMarker;
}
+bool PackageManagerCorePrivate::isOfflineGenerator() const
+{
+ return m_offlineGenerator;
+}
+
bool PackageManagerCorePrivate::statusCanceledOrFailed() const
{
return m_status == PackageManagerCore::Canceled || m_status == PackageManagerCore::Failure;
@@ -758,6 +774,18 @@ QString PackageManagerCorePrivate::maintenanceToolName() const
return QString::fromLatin1("%1/%2").arg(targetDir()).arg(filename);
}
+QString PackageManagerCorePrivate::offlineBinaryName() const
+{
+ QString filename = m_core->value(scOfflineBinaryName, qApp->applicationName()
+ + QLatin1String("_offline-") + QDate::currentDate().toString(Qt::ISODate));
+#if defined(Q_OS_WIN)
+ const QString suffix = QLatin1String(".exe");
+ if (!filename.endsWith(suffix))
+ filename += suffix;
+#endif
+ return QString::fromLatin1("%1/%2").arg(targetDir()).arg(filename);
+}
+
static QNetworkProxy readProxy(QXmlStreamReader &reader)
{
QNetworkProxy proxy(QNetworkProxy::HttpProxy);
@@ -1477,6 +1505,58 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper
m_needToWriteMaintenanceTool = false;
}
+void PackageManagerCorePrivate::writeOfflineBaseBinary()
+{
+ qint64 size;
+ QFile input(installerBinaryPath());
+
+ QInstaller::openForRead(&input);
+#ifndef Q_OS_MACOS
+ BinaryLayout layout = BinaryContent::binaryLayout(&input, BinaryContent::MagicCookie);
+ size = layout.endOfExectuable;
+#else
+ // On macOS the data is on a separate file so we can just get the size
+ size = input.size();
+#endif
+
+ const QString offlineBinaryTempName = offlineBinaryName() + QLatin1String(".new");
+ qCDebug(QInstaller::lcInstallerInstallLog) << "Writing offline base binary:" << offlineBinaryTempName;
+ ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Writing offline base binary."));
+
+ QFile out(generateTemporaryFileName());
+ QInstaller::openForWrite(&out); // throws an exception in case of error
+
+ if (!input.seek(0))
+ throw Error(tr("Failed to seek in file %1: %2").arg(input.fileName(), input.errorString()));
+
+ QInstaller::appendData(&out, &input, size);
+
+ {
+ // Check if we have an existing binary, for any reason
+ QFile dummy(offlineBinaryTempName);
+ if (dummy.exists() && !dummy.remove()) {
+ throw Error(tr("Cannot remove file \"%1\": %2").arg(dummy.fileName(),
+ dummy.errorString()));
+ }
+ // Offline binary name might contain non-existing leading directories
+ const QString offlineBinaryAbsolutePath = QFileInfo(offlineBinaryTempName).absolutePath();
+ QDir dummyDir(offlineBinaryAbsolutePath);
+ if (!dummyDir.exists() && !dummyDir.mkpath(offlineBinaryAbsolutePath)) {
+ throw Error(tr("Cannot create directory \"%1\".").arg(dummyDir.absolutePath()));
+ }
+ }
+
+ if (!out.copy(offlineBinaryTempName)) {
+ throw Error(tr("Cannot write offline binary to \"%1\": %2").arg(offlineBinaryTempName,
+ out.errorString()));
+ }
+
+ if (out.exists() && !out.remove()) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << tr("Cannot remove temporary file \"%1\": %2")
+ .arg(out.fileName(), out.errorString());
+ }
+}
+
QString PackageManagerCorePrivate::registerPath()
{
#ifdef Q_OS_WIN
@@ -1930,6 +2010,138 @@ bool PackageManagerCorePrivate::runUninstaller()
return success;
}
+bool PackageManagerCorePrivate::runOfflineGenerator()
+{
+ const QString offlineBinaryTempName = offlineBinaryName() + QLatin1String(".new");
+ const QString tempSettingsFilePath = generateTemporaryFileName()
+ + QDir::separator() + QLatin1String("config.xml");
+
+ bool adminRightsGained = false;
+ try {
+ setStatus(PackageManagerCore::Running);
+ emit offlineGenerationStarted(); // Resets also the ProgressCoordninator
+
+ // Never write the maintenance tool when generating offline installer
+ m_needToWriteMaintenanceTool = false;
+
+ // Reserve some progress for the final writing, it should take
+ // only a fraction of time spent in the download part
+ ProgressCoordinator::instance()->addReservePercentagePoints(1);
+
+ const QString target = QDir::cleanPath(targetDir().replace(QLatin1Char('\\'), QLatin1Char('/')));
+ if (target.isEmpty())
+ throw Error(tr("Variable 'TargetDir' not set."));
+
+ // Create target directory for installer to be generated
+ if (!QDir(target).exists()) {
+ if (!QDir().mkpath(target)) {
+ adminRightsGained = m_core->gainAdminRights();
+ // Try again with admin privileges
+ if (!QDir().mkpath(target))
+ throw Error(tr("Cannot create target directory for installer."));
+ }
+ } else if (QDir(target).exists()) {
+ if (!directoryWritable(targetDir()))
+ adminRightsGained = m_core->gainAdminRights();
+ }
+ setDefaultFilePermissions(target, DefaultFilePermissions::Executable);
+
+ // Show that there was some work
+ ProgressCoordinator::instance()->addManualPercentagePoints(1);
+ ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Preparing offline generation..."));
+
+ const QList<Component*> componentsToInclude = m_core->orderedComponentsToInstall();
+ qCDebug(QInstaller::lcInstallerInstallLog) << "Included components:" << componentsToInclude.size();
+
+ // Give full part progress size as this is the most time consuming step
+ m_core->downloadNeededArchives(double(1));
+
+ const QString installerBaseReplacement = replaceVariables(m_offlineBaseBinaryUnreplaced);
+ if (!installerBaseReplacement.isEmpty() && QFileInfo(installerBaseReplacement).exists()) {
+ qCDebug(QInstaller::lcInstallerInstallLog) << "Got a replacement installer base binary:"
+ << offlineBinaryTempName;
+
+ if (!QFile::copy(installerBaseReplacement, offlineBinaryTempName)) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << QString::fromLatin1("Cannot copy "
+ "replacemement binary to temporary location \"%1\" from \"%2\".")
+ .arg(offlineBinaryTempName, installerBaseReplacement);
+ }
+ } else {
+ writeOfflineBaseBinary();
+ }
+
+ ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Preparing installer configuration..."));
+
+ // Create copy of internal config file and data, remove repository related elements
+ QInstaller::trimmedCopyConfigData(m_data.settingsFilePath(), tempSettingsFilePath,
+ QStringList() << scRemoteRepositories << scRepositoryCategories);
+
+ // Assemble final installer binary
+ QInstallerTools::BinaryCreatorArgs args;
+ args.target = offlineBinaryName();
+#ifdef Q_OS_MACOS
+ // Target is a disk image on macOS
+ args.target.append(QLatin1String(".dmg"));
+#endif
+ args.templateBinary = offlineBinaryTempName;
+ args.offlineOnly = true;
+ args.configFile = tempSettingsFilePath;
+ args.ftype = QInstallerTools::Include;
+ // Add possible custom resources
+ if (!m_offlineGeneratorResourceCollections.isEmpty())
+ args.resources = m_offlineGeneratorResourceCollections;
+
+ foreach (auto component, componentsToInclude) {
+ args.filteredPackages.append(component->name());
+ args.repositoryDirectories.append(component->localTempPath());
+ }
+ args.repositoryDirectories.removeDuplicates();
+
+ ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Creating the installer..."));
+
+ QString errorMessage;
+ if (QInstallerTools::createBinary(args, errorMessage) == EXIT_FAILURE) {
+ throw Error(tr("Failed to create offline installer. %1").arg(errorMessage));
+ } else {
+ setStatus(PackageManagerCore::Success);
+ }
+
+ // Fake a possible wrong value to show a full progress
+ const int progress = ProgressCoordinator::instance()->progressInPercentage();
+ // This should be only the reserved one (1) from the beginning
+ if (progress < 100)
+ ProgressCoordinator::instance()->addManualPercentagePoints(100 - progress);
+
+ } catch (const Error &err) {
+ if (m_core->status() != PackageManagerCore::Canceled) {
+ setStatus(PackageManagerCore::Failure);
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
+ QLatin1String("installationError"), tr("Error"), err.message());
+ }
+ }
+ QFile tempBinary(offlineBinaryTempName);
+ if (tempBinary.exists() && !tempBinary.remove()) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << tr("Cannot remove temporary file \"%1\": %2")
+ .arg(tempBinary.fileName(), tempBinary.errorString());
+ }
+
+ QDir tempSettingsDir(QFileInfo(tempSettingsFilePath).absolutePath());
+ if (tempSettingsDir.exists() && !tempSettingsDir.removeRecursively()) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << tr("Cannot remove temporary directory \"%1\".")
+ .arg(tempSettingsDir.path());
+ }
+
+ const bool success = m_core->status() == PackageManagerCore::Success;
+ if (adminRightsGained)
+ m_core->dropAdminRights();
+
+ ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QString::fromLatin1("\n%1").arg(
+ success ? tr("Offline generation completed successfully.") : tr("Offline generation aborted!")));
+
+ emit offlineGenerationFinished();
+ return success;
+}
+
void PackageManagerCorePrivate::installComponent(Component *component, double progressOperationSize,
bool adminRightsGained)
{
diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h
index 8b24cf2d0..8983a95c2 100644
--- a/src/libs/installer/packagemanagercore_p.h
+++ b/src/libs/installer/packagemanagercore_p.h
@@ -96,11 +96,13 @@ public:
QString maintenanceToolName() const;
QString installerBinaryPath() const;
+ QString offlineBinaryName() const;
void writeMaintenanceConfigFiles();
void readMaintenanceConfigFiles(const QString &targetDir);
void writeMaintenanceTool(OperationList performedOperations);
+ void writeOfflineBaseBinary();
QString componentsXmlPath() const;
QString configurationFileName() const;
@@ -133,6 +135,9 @@ public:
bool runPackageUpdater();
bool isPackageManager() const;
+ bool runOfflineGenerator();
+ bool isOfflineGenerator() const;
+
QString replaceVariables(const QString &str) const;
QByteArray replaceVariables(const QByteArray &str) const;
@@ -171,6 +176,8 @@ signals:
void installationFinished();
void uninstallationStarted();
void uninstallationFinished();
+ void offlineGenerationStarted();
+ void offlineGenerationFinished();
public:
UpdateFinder *m_updateFinder;
@@ -194,6 +201,8 @@ public:
bool m_needToWriteMaintenanceTool;
PackageManagerCoreData m_data;
QString m_installerBaseBinaryUnreplaced;
+ QString m_offlineBaseBinaryUnreplaced;
+ QStringList m_offlineGeneratorResourceCollections;
QList<QInstaller::Component*> m_rootComponents;
QList<QInstaller::Component*> m_rootDependencyReplacements;
@@ -263,6 +272,7 @@ private:
qint64 m_magicBinaryMarker;
bool m_componentsToInstallCalculated;
bool m_foundEssentialUpdate;
+ bool m_offlineGenerator;
mutable ScriptEngine *m_componentScriptEngine;
mutable ScriptEngine *m_controlScriptEngine;
diff --git a/src/libs/installer/packagemanagercoredata.cpp b/src/libs/installer/packagemanagercoredata.cpp
index 9f8ec7014..149911d61 100644
--- a/src/libs/installer/packagemanagercoredata.cpp
+++ b/src/libs/installer/packagemanagercoredata.cpp
@@ -70,8 +70,9 @@ PackageManagerCoreData::PackageManagerCoreData(const QHash<QString, QString> &va
// TODO: add more platforms as needed...
#endif
- m_settings = Settings::fromFileAndPrefix(QLatin1String(":/metadata/installer-config/config.xml"),
- QLatin1String(":/metadata/installer-config/"), Settings::RelaxedParseMode);
+ m_settingsFilePath = QLatin1String(":/metadata/installer-config/config.xml");
+ m_settings = Settings::fromFileAndPrefix(m_settingsFilePath,
+ QFileInfo(m_settingsFilePath).absolutePath(), Settings::RelaxedParseMode);
// fill the variables defined in the settings
m_variables.insert(QLatin1String("ProductName"), m_settings.applicationName());
diff --git a/src/libs/installer/packagemanagercoredata.h b/src/libs/installer/packagemanagercoredata.h
index ec0d4c302..e112fea1c 100644
--- a/src/libs/installer/packagemanagercoredata.h
+++ b/src/libs/installer/packagemanagercoredata.h
@@ -45,6 +45,10 @@ public:
Settings &settings() const;
QStringList keys() const;
+ inline QString settingsFilePath() {
+ return m_settingsFilePath;
+ }
+
bool contains(const QString &key) const;
bool setValue(const QString &key, const QString &normalizedValue);
QVariant value(const QString &key, const QVariant &_default = QVariant()) const;
@@ -54,6 +58,7 @@ public:
private:
mutable Settings m_settings;
+ QString m_settingsFilePath;
QHash<QString, QString> m_variables;
};
diff --git a/src/libs/installer/settings.cpp b/src/libs/installer/settings.cpp
index 5d8bde36a..7c1400aed 100644
--- a/src/libs/installer/settings.cpp
+++ b/src/libs/installer/settings.cpp
@@ -66,8 +66,6 @@ static const QLatin1String scMaintenanceToolName("MaintenanceToolName");
static const QLatin1String scUserRepositories("UserRepositories");
static const QLatin1String scTmpRepositories("TemporaryRepositories");
static const QLatin1String scMaintenanceToolIniFile("MaintenanceToolIniFile");
-static const QLatin1String scRemoteRepositories("RemoteRepositories");
-static const QLatin1String scRepositoryCategories("RepositoryCategories");
static const QLatin1String scDependsOnLocalInstallerBinary("DependsOnLocalInstallerBinary");
static const QLatin1String scTranslations("Translations");
static const QLatin1String scCreateLocalRepository("CreateLocalRepository");
diff --git a/src/sdk/commandlineinterface.cpp b/src/sdk/commandlineinterface.cpp
index f522f7df6..7bb76d2af 100644
--- a/src/sdk/commandlineinterface.cpp
+++ b/src/sdk/commandlineinterface.cpp
@@ -210,6 +210,37 @@ int CommandLineInterface::removeInstallation()
}
}
+int CommandLineInterface::createOfflineInstaller()
+{
+ if (!initialize())
+ return EXIT_FAILURE;
+ if (!m_core->isInstaller() || m_core->isOfflineOnly()) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "Offline installer can only be created with an online installer.";
+ return EXIT_FAILURE;
+ }
+ if (m_positionalArguments.isEmpty()) {
+ qCWarning(QInstaller::lcInstallerInstallLog) << "Missing components argument.";
+ return EXIT_FAILURE;
+ }
+ if (!(setTargetDir() && checkLicense()))
+ return EXIT_FAILURE;
+
+ if (m_parser.isSet(CommandLineOptions::scOfflineInstallerNameLong)) {
+ m_core->setOfflineBinaryName(m_parser.value(CommandLineOptions::scOfflineInstallerNameLong));
+ } else {
+ const QString offlineName = m_core->offlineBinaryName();
+ qCDebug(QInstaller::lcInstallerInstallLog) << "No filename specified for "
+ "the generated installer, using default name:" << offlineName;
+ }
+ try {
+ return m_core->createOfflineInstaller(m_positionalArguments);
+ } catch (const QInstaller::Error &err) {
+ qCCritical(QInstaller::lcInstallerInstallLog) << err.message();
+ return EXIT_FAILURE;
+ }
+}
+
bool CommandLineInterface::checkLicense()
{
const ProductKeyCheck *const productKeyCheck = ProductKeyCheck::instance();
diff --git a/src/sdk/commandlineinterface.h b/src/sdk/commandlineinterface.h
index 9833ad871..2627bc6f8 100644
--- a/src/sdk/commandlineinterface.h
+++ b/src/sdk/commandlineinterface.h
@@ -48,6 +48,7 @@ public:
int installPackages();
int uninstallPackages();
int removeInstallation();
+ int createOfflineInstaller();
private:
bool initialize();
diff --git a/src/sdk/main.cpp b/src/sdk/main.cpp
index 8c21cc39a..076458173 100644
--- a/src/sdk/main.cpp
+++ b/src/sdk/main.cpp
@@ -263,6 +263,9 @@ int main(int argc, char *argv[])
} else if (parser.positionalArguments().contains(CommandLineOptions::scPurgeShort)
|| parser.positionalArguments().contains(CommandLineOptions::scPurgeLong)){
return CommandLineInterface(argc, argv).removeInstallation();
+ } else if (parser.positionalArguments().contains(CommandLineOptions::scCreateOfflineShort)
+ || parser.positionalArguments().contains(CommandLineOptions::scCreateOfflineLong)) {
+ return CommandLineInterface(argc, argv).createOfflineInstaller();
}
if (QInstaller::isVerbose()) {
std::cout << VERSION << std::endl << BUILDDATE << std::endl << SHA << std::endl;
diff --git a/tests/auto/installer/cliinterface/tst_cliinterface.cpp b/tests/auto/installer/cliinterface/tst_cliinterface.cpp
index e19a37288..c3e1eff57 100644
--- a/tests/auto/installer/cliinterface/tst_cliinterface.cpp
+++ b/tests/auto/installer/cliinterface/tst_cliinterface.cpp
@@ -102,22 +102,22 @@ private slots:
QLoggingCategory::setFilterRules(loggingRules);
QTest::ignoreMessage(QtDebugMsg, "Preparing meta information download...");
- QTest::ignoreMessage(QtDebugMsg, "Cannot install component A. Component is installed only as automatic dependency to autoDep.");
+ QTest::ignoreMessage(QtDebugMsg, "Cannot install component A. Component is installed only as automatic dependency to autoDep.\n");
QCOMPARE(PackageManagerCore::Success, core->installSelectedComponentsSilently(QStringList()
<< QLatin1String("A")));
QTest::ignoreMessage(QtDebugMsg, "Preparing meta information download...");
- QTest::ignoreMessage(QtDebugMsg, "Cannot install component AB. Component is not checkable meaning you have to select one of the subcomponents.");
+ QTest::ignoreMessage(QtDebugMsg, "Cannot install component AB. Component is not checkable meaning you have to select one of the subcomponents.\n");
QCOMPARE(PackageManagerCore::Success, core->installSelectedComponentsSilently(QStringList()
<< QLatin1String("AB")));
QTest::ignoreMessage(QtDebugMsg, "Preparing meta information download...");
- QTest::ignoreMessage(QtDebugMsg, "Cannot install B. Component is virtual.");
+ QTest::ignoreMessage(QtDebugMsg, "Cannot install B. Component is virtual.\n");
QCOMPARE(PackageManagerCore::Success, core->installSelectedComponentsSilently(QStringList()
<< QLatin1String("B")));
QTest::ignoreMessage(QtDebugMsg, "Preparing meta information download...");
- QTest::ignoreMessage(QtDebugMsg, "Cannot install MissingComponent. Component not found.");
+ QTest::ignoreMessage(QtDebugMsg, "Cannot install MissingComponent. Component not found.\n");
QCOMPARE(PackageManagerCore::Success, core->installSelectedComponentsSilently(QStringList()
<< QLatin1String("MissingComponent")));
QCOMPARE(PackageManagerCore::Success, core->status());