diff options
-rw-r--r-- | doc/scripting-api/packagemanagercore.qdoc | 7 | ||||
-rw-r--r-- | src/libs/installer/elevatedexecuteoperation.cpp | 85 | ||||
-rw-r--r-- | src/libs/installer/elevatedexecuteoperation.h | 8 | ||||
-rw-r--r-- | src/libs/installer/extractarchiveoperation.cpp | 4 | ||||
-rw-r--r-- | src/libs/installer/packagemanagercore.cpp | 15 | ||||
-rw-r--r-- | src/libs/installer/packagemanagercore.h | 1 | ||||
-rw-r--r-- | src/libs/installer/packagemanagercore_p.cpp | 28 | ||||
-rw-r--r-- | src/libs/installer/packagemanagercore_p.h | 12 | ||||
-rw-r--r-- | src/libs/installer/packagemanagercoredata.cpp | 7 | ||||
-rw-r--r-- | src/libs/installer/packagemanagercoredata.h | 3 | ||||
-rw-r--r-- | src/libs/kdtools/updateoperation.h | 6 | ||||
-rw-r--r-- | tests/auto/installer/elevatedexecuteoperation/elevatedexecuteoperation.pro | 8 | ||||
-rw-r--r-- | tests/auto/installer/elevatedexecuteoperation/tst_elevatedexecuteoperation.cpp | 67 | ||||
-rw-r--r-- | tests/auto/installer/installer.pro | 3 |
14 files changed, 202 insertions, 52 deletions
diff --git a/doc/scripting-api/packagemanagercore.qdoc b/doc/scripting-api/packagemanagercore.qdoc index 8a9d297a0..bacf50f27 100644 --- a/doc/scripting-api/packagemanagercore.qdoc +++ b/doc/scripting-api/packagemanagercore.qdoc @@ -728,6 +728,13 @@ */ /*! + \qmlmethod string installer::key(string value) + + Returns the installer key for \a value. If \a value is not known, empty string is + returned. +*/ + +/*! \qmlmethod void installer::setValue(string key, string value) Sets the installer value for \a key to \a value. diff --git a/src/libs/installer/elevatedexecuteoperation.cpp b/src/libs/installer/elevatedexecuteoperation.cpp index 39be98e6f..f81377ac4 100644 --- a/src/libs/installer/elevatedexecuteoperation.cpp +++ b/src/libs/installer/elevatedexecuteoperation.cpp @@ -61,7 +61,11 @@ private: public: void readProcessOutput(); - bool run(const QStringList &arguments); + int run(QStringList &arguments, const OperationType type); + +private: + bool needsRerunWithReplacedVariables(QStringList &arguments, const OperationType type); + QProcessWrapper *process; bool showStandardError; @@ -100,10 +104,10 @@ bool ElevatedExecuteOperation::performOperation() PackageManagerCore *const core = packageManager(); args = core->replaceVariables(args); } - return d->run(args); + return d->run(args, Operation::Perform) ? false : true; } -bool ElevatedExecuteOperation::Private::run(const QStringList &arguments) +int ElevatedExecuteOperation::Private::run(QStringList &arguments, const OperationType type) { QStringList args = arguments; QString workingDirectory; @@ -192,13 +196,17 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments) success = process->waitForFinished(-1); } - bool returnValue = true; + int returnValue = NoError; if (!success) { q->setError(UserDefinedError); //TODO: pass errorString() through the wrapper */ q->setErrorString(tr("Cannot start: \"%1\": %2").arg(callstr, process->errorString())); - returnValue = false; + if (!needsRerunWithReplacedVariables(arguments, type)) { + returnValue = Error; + } else { + returnValue = NeedsRerun; + } } if (QThread::currentThread() == qApp->thread()) { @@ -213,26 +221,29 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments) if (process->exitStatus() == QProcessWrapper::CrashExit) { q->setError(UserDefinedError); q->setErrorString(tr("Program crashed: \"%1\"").arg(callstr)); - returnValue = false; + returnValue = Error; } - if (!allowedExitCodes.contains(process->exitCode())) { - q->setError(UserDefinedError); - if (customErrorMessage.isEmpty()) { - q->setErrorString(tr("Execution failed (Unexpected exit code: %1): \"%2\"") - .arg(QString::number(process->exitCode()), callstr)); + if (!allowedExitCodes.contains(process->exitCode()) && returnValue != NeedsRerun) { + if (!needsRerunWithReplacedVariables(arguments, type)) { + q->setError(UserDefinedError); + if (customErrorMessage.isEmpty()) { + q->setErrorString(tr("Execution failed (Unexpected exit code: %1): \"%2\"") + .arg(QString::number(process->exitCode()), callstr)); + } else { + q->setErrorString(customErrorMessage); + } + + QByteArray standardErrorOutput = process->readAllStandardError(); + // in error case it would be useful to see something in verbose output + if (!standardErrorOutput.isEmpty()) + qCWarning(QInstaller::lcInstallerInstallLog).noquote() << standardErrorOutput; + + returnValue = Error; } else { - q->setErrorString(customErrorMessage); + returnValue = NeedsRerun; } - - QByteArray standardErrorOutput = process->readAllStandardError(); - // in error case it would be useful to see something in verbose output - if (!standardErrorOutput.isEmpty()) - qCWarning(QInstaller::lcInstallerInstallLog).noquote() << standardErrorOutput; - - returnValue = false; } - Q_ASSERT(process); Q_ASSERT(process->state() == QProcessWrapper::NotRunning); delete process; @@ -241,6 +252,28 @@ bool ElevatedExecuteOperation::Private::run(const QStringList &arguments) return returnValue; } +bool ElevatedExecuteOperation::Private::needsRerunWithReplacedVariables(QStringList &arguments, const OperationType type) +{ + if (type != Operation::Undo) + return false; + bool rerun = false; + PackageManagerCore *const core = q->packageManager(); + for (int i = 0; i < arguments.count(); i++) { + QString key = core->key(arguments.at(i)); + if (!key.isEmpty() && key.endsWith(QLatin1String("_OLD"))) { + key.remove(key.length() - 4, 4); + if (core->containsValue(key)) { + key.prepend(QLatin1String("@")); + key.append(QLatin1String("@")); + QString value = core->replaceVariables(key); + arguments.replace(i, value); + rerun = true; + } + } + } + return rerun; +} + /*! Cancels the ElevatedExecuteOperation. This methods tries to terminate the process gracefully by calling QProcessWrapper::terminate. After 10 seconds, the process gets killed. @@ -285,7 +318,17 @@ bool ElevatedExecuteOperation::undoOperation() PackageManagerCore *const core = packageManager(); args = core->replaceVariables(args); } - return d->run(args); + + int returnValue = d->run(args, Operation::Undo); + if (returnValue == NeedsRerun) { + qCDebug(QInstaller::lcInstallerInstallLog).noquote() << QString::fromLatin1("Failed to run " + "undo operation \"%1\" for component %2. Trying again with arguments %3").arg(name(), + value(QLatin1String("component")).toString(), args.join(QLatin1String(", "))); + setError(NoError); + setErrorString(QString()); + returnValue = d->run(args, Operation::Undo); + } + return returnValue ? false : true; } bool ElevatedExecuteOperation::testOperation() diff --git a/src/libs/installer/elevatedexecuteoperation.h b/src/libs/installer/elevatedexecuteoperation.h index 5585e77a8..470d3e506 100644 --- a/src/libs/installer/elevatedexecuteoperation.h +++ b/src/libs/installer/elevatedexecuteoperation.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -38,6 +38,12 @@ class INSTALLER_EXPORT ElevatedExecuteOperation : public QObject, public Operati Q_OBJECT public: + enum Error { + NoError = 0, + Error, + NeedsRerun + }; + explicit ElevatedExecuteOperation(PackageManagerCore *core); ~ElevatedExecuteOperation(); diff --git a/src/libs/installer/extractarchiveoperation.cpp b/src/libs/installer/extractarchiveoperation.cpp index 5b13b1f25..c2d541929 100644 --- a/src/libs/installer/extractarchiveoperation.cpp +++ b/src/libs/installer/extractarchiveoperation.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -163,7 +163,7 @@ bool ExtractArchiveOperation::performOperation() // TODO: Use backups for rollback, too? Doesn't work for uninstallation though. // delete all backups we can delete right now, remember the rest - foreach (const Backup &i, callback.backupFiles()) + foreach (const QInstaller::Backup &i, callback.backupFiles()) deleteFileNowOrLater(i.second); if (!receiver.success()) { diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp index ed513fb69..7eaae7b33 100644 --- a/src/libs/installer/packagemanagercore.cpp +++ b/src/libs/installer/packagemanagercore.cpp @@ -875,7 +875,7 @@ void PackageManagerCore::rollBackInstallation() operation->setValue(QLatin1String("forceremoval"), false); } - PackageManagerCorePrivate::performOperationThreaded(operation, PackageManagerCorePrivate::Undo); + PackageManagerCorePrivate::performOperationThreaded(operation, Operation::Undo); const QString componentName = operation->value(QLatin1String("component")).toString(); if (!componentName.isEmpty()) { @@ -2963,7 +2963,7 @@ bool PackageManagerCore::performOperation(const QString &name, const QStringList op->setArguments(replaceVariables(arguments)); op->backup(); if (!PackageManagerCorePrivate::performOperationThreaded(op.data())) { - PackageManagerCorePrivate::performOperationThreaded(op.data(), PackageManagerCorePrivate::Undo); + PackageManagerCorePrivate::performOperationThreaded(op.data(), Operation::Undo); return false; } return true; @@ -3121,6 +3121,17 @@ QStringList PackageManagerCore::values(const QString &key, const QStringList &de } /*! + Returns the installer key for \a value. If \a value is not known, empty string is + returned. + + \sa {installer::key}{installer.key} +*/ +QString PackageManagerCore::key(const QString &value) const +{ + return d->m_data.key(value); +} + +/*! Sets the installer value for \a key to \a value. \sa {installer::setValue}{installer.setValue} diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h index b83d8f05a..5c480e1a9 100644 --- a/src/libs/installer/packagemanagercore.h +++ b/src/libs/installer/packagemanagercore.h @@ -169,6 +169,7 @@ public: Q_INVOKABLE void setValue(const QString &key, const QString &value); Q_INVOKABLE QString value(const QString &key, const QString &defaultValue = QString()) const; Q_INVOKABLE QStringList values(const QString &key, const QStringList &defaultValue = QStringList()) const; + Q_INVOKABLE QString key(const QString &value) const; QString replaceVariables(const QString &str) const; QByteArray replaceVariables(const QByteArray &str) const; diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp index 274ea017b..94e08018a 100644 --- a/src/libs/installer/packagemanagercore_p.cpp +++ b/src/libs/installer/packagemanagercore_p.cpp @@ -117,18 +117,18 @@ private: Operation *m_operation; }; -static bool runOperation(Operation *operation, PackageManagerCorePrivate::OperationType type) +static bool runOperation(Operation *operation, Operation::OperationType type) { OperationTracer tracer(operation); switch (type) { - case PackageManagerCorePrivate::Backup: + case Operation::Backup: tracer.trace(QLatin1String("backup")); operation->backup(); return true; - case PackageManagerCorePrivate::Perform: + case Operation::Perform: tracer.trace(QLatin1String("perform")); return operation->performOperation(); - case PackageManagerCorePrivate::Undo: + case Operation::Undo: tracer.trace(QLatin1String("undo")); return operation->undoOperation(); default: @@ -368,7 +368,7 @@ bool PackageManagerCorePrivate::isProcessRunning(const QString &name, } /* static */ -bool PackageManagerCorePrivate::performOperationThreaded(Operation *operation, OperationType type) +bool PackageManagerCorePrivate::performOperationThreaded(Operation *operation, Operation::OperationType type) { QFutureWatcher<bool> futureWatcher; const QFuture<bool> future = QtConcurrent::run(runOperation, operation, type); @@ -1254,7 +1254,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper // create the directory containing the maintenance tool (like a bundle structure on macOS...) Operation *op = createOwnedOperation(QLatin1String("Mkdir")); op->setArguments(QStringList() << targetAppDirPath); - performOperationThreaded(op, Backup); + performOperationThreaded(op, Operation::Backup); performOperationThreaded(op); performedOperations.append(takeOwnedOperation(op)); } @@ -1266,14 +1266,14 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper Operation *op = createOwnedOperation(QLatin1String("Copy")); op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../PkgInfo")) << (targetAppDirPath + QLatin1String("/../PkgInfo"))); - performOperationThreaded(op, Backup); + performOperationThreaded(op, Operation::Backup); performOperationThreaded(op); // copy Info.plist to target directory op = createOwnedOperation(QLatin1String("Copy")); op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../Info.plist")) << (targetAppDirPath + QLatin1String("/../Info.plist"))); - performOperationThreaded(op, Backup); + performOperationThreaded(op, Operation::Backup); performOperationThreaded(op); // patch the Info.plist after copying it @@ -1306,7 +1306,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper op = createOwnedOperation(QLatin1String("Mkdir")); op->setArguments(QStringList() << (QFileInfo(targetAppDirPath).path() + QLatin1String("/Resources"))); - performOperationThreaded(op, Backup); + performOperationThreaded(op, Operation::Backup); performOperationThreaded(op); // copy application icons if it exists. @@ -1315,7 +1315,7 @@ void PackageManagerCorePrivate::writeMaintenanceTool(OperationList performedOper op = createOwnedOperation(QLatin1String("Copy")); op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../Resources/") + icon) << (targetAppDirPath + QLatin1String("/../Resources/") + icon)); - performOperationThreaded(op, Backup); + performOperationThreaded(op, Operation::Backup); performOperationThreaded(op); // finally, copy everything within Frameworks and plugins @@ -1617,7 +1617,7 @@ bool PackageManagerCorePrivate::runInstaller() mkdirOp->setValue(QLatin1String("forceremoval"), true); mkdirOp->setValue(QLatin1String("uninstall-only"), true); - performOperationThreaded(mkdirOp, Backup); + performOperationThreaded(mkdirOp, Operation::Backup); if (!performOperationThreaded(mkdirOp)) { // if we cannot create the target dir, we try to activate the admin rights adminRightsGained = m_core->gainAdminRights(); @@ -2177,7 +2177,7 @@ void PackageManagerCorePrivate::installComponent(Component *component, double pr connectOperationCallMethodRequest(operation); // allow the operation to backup stuff before performing the operation - performOperationThreaded(operation, PackageManagerCorePrivate::Backup); + performOperationThreaded(operation, Operation::Backup); bool ignoreError = false; bool ok = performOperationThreaded(operation); @@ -2412,7 +2412,7 @@ void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOpera qCDebug(QInstaller::lcInstallerInstallLog) << "undo operation=" << undoOperation->name(); bool ignoreError = false; - bool ok = performOperationThreaded(undoOperation, PackageManagerCorePrivate::Undo); + bool ok = performOperationThreaded(undoOperation, Operation::Undo); const QString componentName = undoOperation->value(QLatin1String("component")).toString(); @@ -2425,7 +2425,7 @@ void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOpera QMessageBox::Retry | QMessageBox::Ignore, QMessageBox::Ignore); if (button == QMessageBox::Retry) - ok = performOperationThreaded(undoOperation, Undo); + ok = performOperationThreaded(undoOperation, Operation::Undo); else if (button == QMessageBox::Ignore) ignoreError = true; } diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h index 8983a95c2..858baf9eb 100644 --- a/src/libs/installer/packagemanagercore_p.h +++ b/src/libs/installer/packagemanagercore_p.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -67,12 +67,6 @@ class PackageManagerCorePrivate : public QObject Q_DISABLE_COPY(PackageManagerCorePrivate) public: - enum OperationType { - Backup, - Perform, - Undo - }; - explicit PackageManagerCorePrivate(PackageManagerCore *core); explicit PackageManagerCorePrivate(PackageManagerCore *core, qint64 magicInstallerMaker, const QList<OperationBlob> &performedOperations); @@ -80,8 +74,8 @@ public: static bool isProcessRunning(const QString &name, const QList<ProcessInfo> &processes); - static bool performOperationThreaded(Operation *op, PackageManagerCorePrivate::OperationType type - = PackageManagerCorePrivate::Perform); + static bool performOperationThreaded(Operation *op, UpdateOperation::OperationType type + = UpdateOperation::Perform); void initialize(const QHash<QString, QString> ¶ms); bool isOfflineOnly() const; diff --git a/src/libs/installer/packagemanagercoredata.cpp b/src/libs/installer/packagemanagercoredata.cpp index d488731df..2f3dd9204 100644 --- a/src/libs/installer/packagemanagercoredata.cpp +++ b/src/libs/installer/packagemanagercoredata.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -244,6 +244,11 @@ QVariant PackageManagerCoreData::value(const QString &key, const QVariant &_defa return m_settings.value(key, _default); } +QString PackageManagerCoreData::key(const QString &value) const +{ + return m_variables.key(value, QString()); +} + QString PackageManagerCoreData::replaceVariables(const QString &str) const { static const QChar at = QLatin1Char('@'); diff --git a/src/libs/installer/packagemanagercoredata.h b/src/libs/installer/packagemanagercoredata.h index e112fea1c..4c954d943 100644 --- a/src/libs/installer/packagemanagercoredata.h +++ b/src/libs/installer/packagemanagercoredata.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -52,6 +52,7 @@ public: bool contains(const QString &key) const; bool setValue(const QString &key, const QString &normalizedValue); QVariant value(const QString &key, const QVariant &_default = QVariant()) const; + QString key(const QString &value) const; QString replaceVariables(const QString &str) const; QByteArray replaceVariables(const QByteArray &ba) const; diff --git a/src/libs/kdtools/updateoperation.h b/src/libs/kdtools/updateoperation.h index e05fc34d2..a8110791c 100644 --- a/src/libs/kdtools/updateoperation.h +++ b/src/libs/kdtools/updateoperation.h @@ -53,6 +53,12 @@ public: UserDefinedError = 128 }; + enum OperationType { + Backup, + Perform, + Undo + }; + explicit UpdateOperation(QInstaller::PackageManagerCore *core); virtual ~UpdateOperation(); diff --git a/tests/auto/installer/elevatedexecuteoperation/elevatedexecuteoperation.pro b/tests/auto/installer/elevatedexecuteoperation/elevatedexecuteoperation.pro new file mode 100644 index 000000000..9ac3f260a --- /dev/null +++ b/tests/auto/installer/elevatedexecuteoperation/elevatedexecuteoperation.pro @@ -0,0 +1,8 @@ +include(../../qttest.pri) + +QT -= gui +QT += testlib qml + +SOURCES = tst_elevatedexecuteoperation.cpp + +DEFINES += "QMAKE_BINARY=$$fromNativeSeparators($$QMAKE_BINARY)" diff --git a/tests/auto/installer/elevatedexecuteoperation/tst_elevatedexecuteoperation.cpp b/tests/auto/installer/elevatedexecuteoperation/tst_elevatedexecuteoperation.cpp new file mode 100644 index 000000000..aa2849559 --- /dev/null +++ b/tests/auto/installer/elevatedexecuteoperation/tst_elevatedexecuteoperation.cpp @@ -0,0 +1,67 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include <packagemanagercore.h> +#include <elevatedexecuteoperation.h> + +#include <QTest> + +#define QUOTE_(x) #x +#define QUOTE(x) QUOTE_(x) + +using namespace QInstaller; + +class tst_elevatedexecuteoperation : public QObject +{ + Q_OBJECT + +private slots: + void testExecuteOperation() + { + m_core.setValue(QLatin1String("QMAKE_BINARY"), QUOTE(QMAKE_BINARY)); + m_core.setValue(QLatin1String("QMAKE_BINARY_OLD"), QLatin1String("FAKE_QMAKE")); + ElevatedExecuteOperation operation(&m_core); + operation.setArguments(QStringList() << QLatin1String("UNDOEXECUTE") << QLatin1String("FAKE_QMAKE")); + + QTest::ignoreMessage(QtDebugMsg, "\"FAKE_QMAKE\" started, arguments: \"\""); + QString message = "Failed to run undo operation \"Execute\" for component . Trying again with arguments %1"; + QTest::ignoreMessage(QtDebugMsg, qPrintable(message.arg(QUOTE(QMAKE_BINARY)))); + message = "\"%1\" started, arguments: \"\""; + QTest::ignoreMessage(QtDebugMsg, qPrintable(message.arg(QUOTE(QMAKE_BINARY)))); + + QCOMPARE(operation.undoOperation(), true); + QCOMPARE(Operation::Error(operation.error()), Operation::NoError); + } + +private: + PackageManagerCore m_core; +}; + +QTEST_MAIN(tst_elevatedexecuteoperation) + +#include "tst_elevatedexecuteoperation.moc" diff --git a/tests/auto/installer/installer.pro b/tests/auto/installer/installer.pro index 2c55de40c..4421643b8 100644 --- a/tests/auto/installer/installer.pro +++ b/tests/auto/installer/installer.pro @@ -36,7 +36,8 @@ SUBDIRS += \ moveoperation \ environmentvariableoperation \ licenseagreement \ - globalsettingsoperation + globalsettingsoperation \ + elevatedexecuteoperation win32 { SUBDIRS += registerfiletypeoperation \ |