summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArttu Tarkiainen <arttu.tarkiainen@qt.io>2023-05-22 18:46:33 +0300
committerArttu Tarkiainen <arttu.tarkiainen@qt.io>2023-09-14 06:27:48 +0000
commit554aacf002690075872682b03e0bc836cf9cafe8 (patch)
tree5a2cc12d0c488068f3d5a6e2ec1a09a8701c8ffc
parent21e13337359d3d5fe63631127530c18a3191e83d (diff)
Add support for component aliases
Introduce concept of component aliases, which act as an alternative way for referring a set of related components. Component aliases are declared in an alias definition file, which is included to the created installer's binary layout as a resource. The file lists the available aliases, including metadata - such as name, version, and description - and the list of components and other aliases the alias requires. Aliases can be referred only from the CLI for the time being, with the supported commands 'install' and 'search'. Task-number: QTIFW-2978 Change-Id: I281f171cc7d932ce496051d7090ae169a4709eec Reviewed-by: Katja Marttila <katja.marttila@qt.io>
-rw-r--r--doc/examples/aliases.xml29
-rw-r--r--doc/examples/config.xml1
-rw-r--r--doc/includes/IFWDoc1
-rw-r--r--doc/installerfw-reference.qdoc1
-rw-r--r--doc/installerfw-using.qdoc16
-rw-r--r--doc/installerfw.qdoc90
-rw-r--r--src/libs/installer/calculatorbase.h3
-rw-r--r--src/libs/installer/commandlineparser.cpp14
-rw-r--r--src/libs/installer/componentalias.cpp540
-rw-r--r--src/libs/installer/componentalias.h154
-rw-r--r--src/libs/installer/constants.h5
-rw-r--r--src/libs/installer/installer.pro2
-rw-r--r--src/libs/installer/installercalculator.cpp97
-rw-r--r--src/libs/installer/installercalculator.h9
-rw-r--r--src/libs/installer/loggingutils.cpp36
-rw-r--r--src/libs/installer/loggingutils.h2
-rw-r--r--src/libs/installer/packagemanagercore.cpp125
-rw-r--r--src/libs/installer/packagemanagercore.h15
-rw-r--r--src/libs/installer/packagemanagercore_p.cpp99
-rw-r--r--src/libs/installer/packagemanagercore_p.h11
-rw-r--r--src/libs/installer/settings.cpp7
-rw-r--r--src/libs/installer/settings.h2
-rw-r--r--src/sdk/commandlineinterface.cpp12
23 files changed, 1245 insertions, 26 deletions
diff --git a/doc/examples/aliases.xml b/doc/examples/aliases.xml
new file mode 100644
index 000000000..4b7ef0ebd
--- /dev/null
+++ b/doc/examples/aliases.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<Aliases>
+ <Alias>
+ <Name>package-full</Name>
+ <DisplayName>Full installation package</DisplayName>
+ <Description>Complete installation of the product</Description>
+ <Version>1.0.0</Version>
+ <Virtual>false</Virtual>
+ <RequiresAlias>package-essential,package-optional</RequiresAlias>
+ <RequiresComponent>com.vendor.root.extras</RequiresComponent>
+ </Alias>
+ <Alias>
+ <Name>package-essential</Name>
+ <DisplayName>Essential components</DisplayName>
+ <Description>Essential components for the product</Description>
+ <Version>1.0.0</Version>
+ <Virtual>false</Virtual>
+ <RequiresComponent>com.vendor.root.essential</RequiresComponent>
+ </Alias>
+ <Alias>
+ <Name>package-optional</Name>
+ <DisplayName>Optional components</DisplayName>
+ <Description>Optional components for the product</Description>
+ <Version>1.0.0</Version>
+ <Virtual>false</Virtual>
+ <RequiresComponent>com.vendor.root.optional1,com.vendor.root.optional2</RequiresComponent>
+ </Alias>
+</Aliases>
+
diff --git a/doc/examples/config.xml b/doc/examples/config.xml
index ef4598425..b04a413ec 100644
--- a/doc/examples/config.xml
+++ b/doc/examples/config.xml
@@ -28,4 +28,5 @@
<Url>http://www.your-repo-location/packages/</Url>
</Repository>
</RemoteRepositories>
+ <AliasDefinitionsFile>aliases.xml</AliasDefinitionsFile>
</Installer>
diff --git a/doc/includes/IFWDoc b/doc/includes/IFWDoc
index 6e5b4dbd4..a569a2f9e 100644
--- a/doc/includes/IFWDoc
+++ b/doc/includes/IFWDoc
@@ -14,6 +14,7 @@
#include "commandlineparser.h"
#include "componentchecker.h"
#include "component.h"
+#include "componentalias.h"
#include "componentmodel.h"
#include "concurrentoperationrunner.h"
#include "constants.h"
diff --git a/doc/installerfw-reference.qdoc b/doc/installerfw-reference.qdoc
index 9a57d905e..f129f1218 100644
--- a/doc/installerfw-reference.qdoc
+++ b/doc/installerfw-reference.qdoc
@@ -39,6 +39,7 @@
\li \l{Command Line Interface}
\li \l{Configuration File}
\li \l{Package Directory}
+ \li \l{Alias Definition File}
\li \l{Controller Scripting}
\li \l{Component Scripting}
\li \l{Operations}
diff --git a/doc/installerfw-using.qdoc b/doc/installerfw-using.qdoc
index 5f4674449..c1a57f9ad 100644
--- a/doc/installerfw-using.qdoc
+++ b/doc/installerfw-using.qdoc
@@ -408,6 +408,13 @@
installer.exe --root "C:\Users\MyUser\MyInstallation" install
\endcode
+ The install command can be also used for installing component aliases. If component
+ aliases are specified, the aliased components are selected for installation:
+
+ \code
+ maintenancetool.exe install alias1 alias2
+ \endcode
+
\section1 Checking for Available Updates
To print information about available component updates, run the \c check-updates
@@ -461,7 +468,14 @@
information elements with the option, refer to \l{Summary of Package Information File Elements}.
\code
- installer.exe --filter-packages "DisplayName=MyComponent, Version=1.0" search "expression"
+ installer.exe --type package --filter-packages "DisplayName=MyComponent, Version=1.0" search "expression"
+ \endcode
+
+ When the \c{--type} option is omitted, or the value for the option is set to \c alias, the
+ search command will seach for available component aliases instead:
+
+ \code
+ installer.exe search "expression"
\endcode
\section1 Performing Full Uninstallation
diff --git a/doc/installerfw.qdoc b/doc/installerfw.qdoc
index 3387987a7..8daeff0ed 100644
--- a/doc/installerfw.qdoc
+++ b/doc/installerfw.qdoc
@@ -72,6 +72,7 @@
\li \l{Command Line Interface}
\li \l{Configuration File}
\li \l{Package Directory}
+ \li \l{Alias Definition File}
\li \l{Controller Scripting}
\li \l{Component Scripting}
\li \l{Operations}
@@ -255,6 +256,10 @@
\li --cp, --cache-path <path>
\li Sets the path used for local metadata cache. The path must be writable by the current user.
\row
+ \li --type package|alias
+ \li [CLI] Sets the type of the given arguments for commands supporting multiple argument types,
+ like search. Defaults to alias.
+ \row
\li --am, --accept-messages
\li [CLI] Accepts all message queries without user input.
\row
@@ -313,8 +318,8 @@
\li Command
\li Usage
\row
- \li in, install <pkg ...>
- \li Install packages given as an argument. If no packages are given, install the default package set.
+ \li in, install <pkg|alias ...>
+ \li Install packages and aliases given as an argument. If no arguments are given, install the default package set.
\row
\li ch, check-updates
\li Show information about available updates on the maintenance tool.
@@ -325,14 +330,17 @@
\li rm, remove <pkg ...>
\li Uninstall selected packages and their child components.
\row
- \li li, list <regexp>
+ \li li, list <regexp for pkg>
\li List information about currently installed packages.
\row
- \li se, search <regexp>
- \li Search available packages. If no search pattern is given, show all available packages.
+ \li se, search <regexp for pkg|alias>
+ \li Search available aliases or packages. If no search pattern is given, show all available packages.
\note The \c --filter-packages option can be used to specify additional filters for
the search operation. See \l{Summary of Options}.
+
+ \note The \c --type option can be used to specify the content type to search.
+ See \l{Summary of Options}.
\row
\li co, create-offline <pkg ...>
\li Create offline installer from selected packages.
@@ -564,6 +572,11 @@
\li TargetConfigurationFile
\li Filename for the configuration file on the target. Default is components.xml.
\row
+ \li AliasDefinitionsFile
+ \li Filename for a XML document containing the definitions for component aliases.
+ For more information about how to declare component aliases in the file,
+ see \l{Alias Definition File}.
+ \row
\li Translations
\li List of translation files to be used for translating the user interface. To add
several translation files, specify several \c <Translation> child elements that
@@ -608,6 +621,71 @@
*/
/*!
+ \previouspage ifw-globalconfig.html
+ \page ifw-aliasconfig.html
+ \nextpage noninteractive.html
+
+ \title Alias Definition File
+
+ The alias definition file defines the available component aliases and their
+ properties. The file is typically called \c aliases.xml and located in the
+ \c config directory.
+
+ The component names of the Qt Installer Framework follow a domain-like
+ identifier syntax, for example \c com.vendor.root, \c com.vendor.root.subcomponent,
+ and so on. While this allows an easy way to construct a tree from the components when
+ running the installer in graphical mode, the names can be unintuitive for command line usage,
+ where the components are not displayed in a tree view.
+
+ Instead of relying on the domain-like names for CLI usage, the packager can also define component
+ aliases for existing components. An alias is another name for a single component or a collection
+ of components. It can be used to declare alternative names for existing components that are
+ easier to type and combine multiple components under the same alias name, for easier selection.
+
+ The following example shows a possible alias definition file:
+
+ \quotefile examples/aliases.xml
+
+ \section1 Summary of Alias Definition File Elements
+
+ The following table summarizes the elements in the alias definition file.
+
+ \table
+ \header
+ \li Element
+ \li Description
+ \row
+ \li Name
+ \li Name of component alias.
+ \row
+ \li DisplayName
+ \li Human-readable name of the component alias.
+ \row
+ \li Description
+ \li Human-readable description of the component alias.
+ \row
+ \li Version
+ \li Version number of the component alias.
+ \row
+ \li Virtual
+ \li Set to \c true to hide the component alias from the installer. This also
+ makes the alias unselectable by the user.
+ \row
+ \li RequiresComponent
+ \li Comma-separated list of identifiers of components that this
+ component alias requires. The components are selected for installation
+ when the component alias is selected. Note that the components must be
+ selectable by the user, so virtual or otherwise unselectable components
+ cannot be listed as a requirement.
+ \row
+ \li RequiresAlias
+ \li Comma-separated list of aliases that this component alias requires. The
+ required aliases are selected for installation when this component alias
+ is selected.
+ \endtable
+*/
+
+/*!
\previouspage ifw-updates.html
\page ifw-customizing-installers.html
\nextpage Qt Installer Framework Examples
@@ -806,7 +884,7 @@
/*!
\previouspage ifw-globalconfig.html
\page ifw-component-description.html
- \nextpage noninteractive.html
+ \nextpage ifw-aliasconfig.html
\title Package Directory
diff --git a/src/libs/installer/calculatorbase.h b/src/libs/installer/calculatorbase.h
index baf51c5b4..351658f99 100644
--- a/src/libs/installer/calculatorbase.h
+++ b/src/libs/installer/calculatorbase.h
@@ -49,7 +49,8 @@ public:
VirtualDependent, // "No dependencies to virtual component"
Dependent, // "Added as dependency for %1." / "Removed as dependency component is removed"
Automatic, // "Component(s) added as automatic dependencies" / "Removed as autodependency component is removed"
- Resolved // "Component(s) that have resolved Dependencies"
+ Resolved, // "Component(s) that have resolved Dependencies"
+ Alias // "Components added from selected alias"
};
CalculatorBase(PackageManagerCore *core);
diff --git a/src/libs/installer/commandlineparser.cpp b/src/libs/installer/commandlineparser.cpp
index a91ab0128..94b611334 100644
--- a/src/libs/installer/commandlineparser.cpp
+++ b/src/libs/installer/commandlineparser.cpp
@@ -48,7 +48,7 @@ CommandLineParser::CommandLineParser()
"headless mode. The installation operations can be invoked with the following commands and "
"options. Note that the options marked with \"CLI\" are available in the headless mode only.\n")
+ QLatin1String("\nCommands:\n")
- + indent + QString::fromLatin1("%1, %2 - install default or selected packages - <pkg ...>\n")
+ + indent + QString::fromLatin1("%1, %2 - install default or selected packages and aliases - <pkg|alias ...>\n")
.arg(CommandLineOptions::scInstallShort, CommandLineOptions::scInstallLong)
+ indent + QString::fromLatin1("%1, %2 - show available updates information on maintenance tool\n")
.arg(CommandLineOptions::scCheckUpdatesShort, CommandLineOptions::scCheckUpdatesLong)
@@ -56,13 +56,16 @@ CommandLineParser::CommandLineParser()
.arg(CommandLineOptions::scUpdateShort, CommandLineOptions::scUpdateLong)
+ indent + QString::fromLatin1("%1, %2 - uninstall packages and their child components - <pkg ...>\n")
.arg(CommandLineOptions::scRemoveShort, CommandLineOptions::scRemoveLong)
- + indent + QString::fromLatin1("%1, %2 - list currently installed packages - <regexp>\n")
+ + indent + QString::fromLatin1("%1, %2 - list currently installed packages - <regexp for pkg>\n")
.arg(CommandLineOptions::scListShort, CommandLineOptions::scListLong)
- + indent + QString::fromLatin1("%1, %2 - search available packages - <regexp>\n")
+ + indent + QString::fromLatin1("%1, %2 - search available aliases or packages - <regexp for pkg|alias>\n")
.arg(CommandLineOptions::scSearchShort, CommandLineOptions::scSearchLong)
+ indent + indent + QString::fromLatin1("Note: The --%1 option can be used to specify\n")
.arg(CommandLineOptions::scFilterPackagesLong)
+ indent + indent + QLatin1String("additional filters for the search operation\n")
+ + indent + indent + QString::fromLatin1("Note: The --%1 option can be used to specify\n")
+ .arg(CommandLineOptions::scTypeLong)
+ + indent + indent + QLatin1String("the content type to search\n")
+ indent + QString::fromLatin1("%1, %2 - create offline installer from selected packages - <pkg ...>\n")
.arg(CommandLineOptions::scCreateOfflineShort, CommandLineOptions::scCreateOfflineLong)
+ indent + QString::fromLatin1("%1, %2 - clear contents of the local metadata cache\n")
@@ -177,6 +180,11 @@ CommandLineParser::CommandLineParser()
<< CommandLineOptions::scLocalCachePathShort << CommandLineOptions::scLocalCachePathLong,
QLatin1String("Sets the path used for local metadata cache. The path must be writable by the current user."),
QLatin1String("path")));
+ addOption(QCommandLineOption(QStringList()
+ << CommandLineOptions::scTypeLong,
+ QLatin1String("[CLI] Sets the type of the given arguments for commands supporting multiple argument types, "
+ "like \"search\". Defaults to \"alias\"."),
+ QLatin1String("package|alias")));
// Message query options
addOptionWithContext(QCommandLineOption(QStringList() << CommandLineOptions::scAcceptMessageQueryShort
diff --git a/src/libs/installer/componentalias.cpp b/src/libs/installer/componentalias.cpp
new file mode 100644
index 000000000..6207e7fe6
--- /dev/null
+++ b/src/libs/installer/componentalias.cpp
@@ -0,0 +1,540 @@
+/**************************************************************************
+**
+** Copyright (C) 2023 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 "componentalias.h"
+
+#include "constants.h"
+#include "globals.h"
+#include "packagemanagercore.h"
+#include "updater.h"
+
+namespace QInstaller {
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::AliasSource
+ \brief Describes a source for alias declarations.
+*/
+
+/*!
+ \enum QInstaller::AliasSource::SourceFileFormat
+
+ This enum type holds the possible file formats for alias source:
+
+ \value Unknown
+ Invalid or unknown file format.
+ \value Xml
+ XML file format.
+*/
+
+/*!
+ Constructs an alias source with empty information.
+*/
+AliasSource::AliasSource()
+ : priority(-1)
+{}
+
+/*!
+ Constructs an alias source with source file format \a aFormat, filename \a aFilename, and priority \a aPriority.
+*/
+AliasSource::AliasSource(SourceFileFormat aFormat, const QString &aFilename, int aPriority)
+ : format(aFormat)
+ , filename(aFilename)
+ , priority(aPriority)
+{}
+
+/*!
+ Copy-constructs an alias source from \a other.
+*/
+AliasSource::AliasSource(const AliasSource &other)
+{
+ format = other.format;
+ filename = other.filename;
+ priority = other.priority;
+}
+
+/*!
+ Returns the hash value for the \a key, using \a seed to seed the calculation.
+*/
+uint qHash(const AliasSource &key, uint seed)
+{
+ return qHash(key.filename, seed) ^ key.priority;
+}
+
+/*!
+ Returns \c true if \a lhs and \a rhs are equal; otherwise returns \c false.
+*/
+bool operator==(const AliasSource &lhs, const AliasSource &rhs)
+{
+ return lhs.filename == rhs.filename && lhs.priority == rhs.priority && lhs.format == rhs.format;
+}
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::AliasFinder
+ \brief Creates component alias objects from parsed alias source files, based
+ on version and source priorities.
+*/
+
+/*!
+ Constructs a new alias finder with \a core as the package manager instance.
+*/
+AliasFinder::AliasFinder(PackageManagerCore *core)
+ : m_core(core)
+{
+}
+
+/*!
+ Destroys the finder and cleans unreleased results.
+*/
+AliasFinder::~AliasFinder()
+{
+ clear();
+}
+
+/*!
+ Runs the finder. Parses the alias source files and creates component alias
+ objects based on the parsed data. Same alias may be declared in multiple source
+ files, thus source priority and version information is used to decide which
+ source is used for creating the alias object.
+
+ Any previous results are cleared when calling this.
+
+ Returns \c true if at least one alias was found, \c false otherwise.
+*/
+bool AliasFinder::run()
+{
+ clear();
+
+ if (m_sources.isEmpty())
+ return false;
+
+ // 1. Parse source files
+ for (auto &source : qAsConst(m_sources)) {
+ if (source.format == AliasSource::SourceFileFormat::Unknown) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "Unknown alias source format for file:" << source.filename;
+ continue;
+ }
+ if (source.format == AliasSource::SourceFileFormat::Xml)
+ parseXml(source);
+ }
+
+ // 2. Create aliases based on priority & version
+ for (auto &data : qAsConst(m_aliasData)) {
+ const QString name = data.value(scName).toString();
+ const Resolution resolution = checkPriorityAndVersion(data);
+ if (resolution == Resolution::KeepExisting)
+ continue;
+
+ if (resolution == Resolution::RemoveExisting)
+ delete m_aliases.take(name);
+
+ ComponentAlias *alias = new ComponentAlias(m_core);
+ for (auto &value : data) {
+ if (value.canConvert<QString>())
+ alias->setValue(data.key(value), value.toString());
+ }
+ m_aliases.insert(name, alias);
+ }
+
+ return !m_aliases.isEmpty();
+}
+
+/*!
+ Returns a list of the found aliases.
+*/
+QList<ComponentAlias *> AliasFinder::aliases() const
+{
+ return m_aliases.values();
+}
+
+/*!
+ Sets the alias sources to look alias information from to \a sources.
+*/
+void AliasFinder::setAliasSources(const QSet<AliasSource> &sources)
+{
+ clear();
+ m_sources = sources;
+}
+
+/*!
+ Clears the results of the finder.
+*/
+void AliasFinder::clear()
+{
+ qDeleteAll(m_aliases);
+
+ m_aliases.clear();
+ m_aliasData.clear();
+}
+
+/*!
+ Reads an XML file specified by \a filename, and constructs a variant map of
+ the data for each alias.
+
+ Returns \c true on success, \c false otherwise.
+*/
+bool AliasFinder::parseXml(AliasSource source)
+{
+ QFile file(source.filename);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "Cannot open alias definition for reading:" << file.errorString();
+ return false;
+ }
+
+ QString error;
+ int errorLine;
+ int errorColumn;
+
+ QDomDocument doc;
+ if (!doc.setContent(&file, &error, &errorLine, &errorColumn)) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "Cannot read alias definition document:" << error
+ << "line:" << errorLine << "column:" << errorColumn;
+ return false;
+ }
+ file.close();
+
+ const QDomElement root = doc.documentElement();
+ const QDomNodeList children = root.childNodes();
+
+ for (int i = 0; i < children.count(); ++i) {
+ const QDomElement el = children.at(i).toElement();
+ const QString tag = el.tagName();
+ if (el.isNull() || tag != scAlias) {
+ qCWarning(lcInstallerInstallLog) << "Unexpected element name:" << tag;
+ continue;
+ }
+
+ AliasData data;
+ data.insert(QLatin1String("source"), QVariant::fromValue(source));
+
+ const QDomNodeList c2 = el.childNodes();
+ for (int j = 0; j < c2.count(); ++j) {
+ const QDomElement el2 = c2.at(j).toElement();
+ const QString tag2 = el2.tagName();
+ if (tag2 != scName
+ && tag2 != scDisplayName
+ && tag2 != scDescription
+ && tag2 != scVersion
+ && tag2 != scVirtual
+ && tag2 != scRequiresComponent
+ && tag2 != scRequiresAlias) {
+ qCWarning(lcInstallerInstallLog) << "Unexpected element name:" << tag2;
+ continue;
+ }
+ data.insert(tag2, el2.text());
+ }
+
+ m_aliasData.insert(data.value(scName).toString(), data);
+ }
+
+ return true;
+}
+
+/*!
+ Checks whether \a data should be used for creating a new alias object,
+ based on version and source priority.
+
+ If an alias of the same name exists, always use the one with the higher
+ version. If the new alias has the same version but a higher
+ priority, use the new new alias. Otherwise keep the already existing alias.
+
+ Returns the resolution of the check.
+*/
+AliasFinder::Resolution AliasFinder::checkPriorityAndVersion(const AliasData &data) const
+{
+ for (const auto &existingData : m_aliasData.values(data.value(scName).toString())) {
+ if (existingData == data)
+ continue;
+
+ const int versionMatch = KDUpdater::compareVersion(data.value(scVersion).toString(),
+ existingData.value(scVersion).toString());
+
+ const AliasSource newSource = data.value(QLatin1String("source")).value<AliasSource>();
+ const AliasSource oldSource = existingData.value(QLatin1String("source")).value<AliasSource>();
+
+ if (versionMatch > 0) {
+ // new alias has higher version, use
+ qCDebug(QInstaller::lcDeveloperBuild).nospace() << "Remove Alias 'Name: "
+ << data.value(scName).toString() << ", Version: " << existingData.value(scVersion).toString()
+ << ", Source: " << oldSource.filename
+ << "' found an alias with higher version 'Name: "
+ << data.value(scName).toString() << ", Version: " << data.value(scVersion).toString()
+ << ", Source: " << newSource.filename << "'";
+
+ return Resolution::RemoveExisting;
+ }
+
+ if ((versionMatch == 0) && (newSource.priority > oldSource.priority)) {
+ // new alias version equals but priority is higher, use
+ qCDebug(QInstaller::lcDeveloperBuild).nospace() << "Remove Alias 'Name: "
+ << data.value(scName).toString() << ", Priority: " << oldSource.priority
+ << ", Source: " << oldSource.filename
+ << "' found an alias with higher priority 'Name: "
+ << data.value(scName).toString() << ", Priority: " << newSource.priority
+ << ", Source: " << newSource.filename << "'";
+
+ return Resolution::RemoveExisting;
+ }
+
+ return Resolution::KeepExisting; // otherwise keep existing
+ }
+
+ return Resolution::AddNew;
+}
+
+/*!
+ \inmodule QtInstallerFramework
+ \class QInstaller::ComponentAlias
+ \brief The ComponentAlias class represents an alias for single or multiple components.
+*/
+
+/*!
+ \enum QInstaller::ComponentAlias::UnstableError
+
+ This enum type holds the possible reasons for marking an alias unstable:
+
+ \value ReferenceToUnstable
+ Alias requires another alias that is marked unstable.
+ \value MissingComponent
+ Alias requires a component that is missing.
+ \value UnselectableComponent
+ Alias requires a component that cannot be selected.
+ \value MissingAlias
+ Alias requires another alias that is missing.
+ \value ComponentNameConfict
+ Alias has a name that conflicts with a name of a component
+*/
+
+/*!
+ Constructs a new component alias with \a core as the package manager instance.
+*/
+ComponentAlias::ComponentAlias(PackageManagerCore *core)
+ : m_core(core)
+ , m_selected(false)
+ , m_unstable(false)
+{
+}
+
+/*!
+ Destructs the alias.
+*/
+ComponentAlias::~ComponentAlias()
+{
+}
+
+/*!
+ Returns the name of the alias.
+*/
+QString ComponentAlias::name() const
+{
+ return m_variables.value(scName);
+}
+
+/*!
+ Returns the display name of the alias.
+*/
+QString ComponentAlias::displayName() const
+{
+ return m_variables.value(scDisplayName);
+}
+
+/*!
+ Returns the description text of the alias.
+*/
+QString ComponentAlias::description() const
+{
+ return m_variables.value(scDescription);
+}
+
+/*!
+ Returns the version of the alias.
+*/
+QString ComponentAlias::version() const
+{
+ return m_variables.value(scVersion);
+}
+
+/*!
+ Returns \c true if the alias is virtual, \c false otherwise.
+
+ Virtual aliases are aliases that cannot be selected by the
+ user, and are invisible. They can be required by other aliases however.
+*/
+bool ComponentAlias::isVirtual() const
+{
+ return m_variables.value(scVirtual, scFalse).toLower() == scTrue;
+}
+
+/*!
+ Returns \c true if the alias is selected for installation, \c false otherwise.
+*/
+bool ComponentAlias::isSelected() const
+{
+ return m_selected;
+}
+
+/*!
+ Sets the selection state of the alias to \a selected. The selection
+ does not have an effect if the alias is unselectable.
+*/
+void ComponentAlias::setSelected(bool selected)
+{
+ if (selected && (isUnstable() || isVirtual()))
+ return;
+
+ m_selected = selected;
+}
+
+/*!
+ Returns the list of components required by this alias, or an
+ empty list if this alias does not require any components.
+*/
+QList<Component *> ComponentAlias::components()
+{
+ if (m_components.isEmpty()) {
+ const QStringList componentList = QInstaller::splitStringWithComma(
+ m_variables.value(scRequiresComponent));
+
+ for (const auto &componentName : componentList) {
+ Component *component = m_core->componentByName(componentName);
+ if (!component) {
+ const QString error = QLatin1String("No required component found by name: ")
+ + componentName;
+ qCWarning(lcInstallerInstallLog) << error;
+
+ setUnstable(UnstableError::MissingComponent, error);
+ continue;
+ }
+
+ if (component->isUnstable() || !component->isCheckable()) {
+ const QString error = QLatin1String("Alias requires component that is uncheckable or unstable: ")
+ + componentName;
+ qCWarning(lcInstallerInstallLog) << error;
+
+ setUnstable(UnstableError::UnselectableComponent, error);
+ continue;
+ }
+
+ m_components.append(component);
+ }
+ }
+
+ return m_components;
+}
+
+/*!
+ Returns the list of other aliases required by this alias, or an
+ empty list if this alias does not require any other aliases.
+*/
+QList<ComponentAlias *> ComponentAlias::aliases()
+{
+ if (m_aliases.isEmpty()) {
+ const QStringList aliasList = QInstaller::splitStringWithComma(
+ m_variables.value(scRequiresAlias));
+
+ for (const auto &aliasName : aliasList) {
+ ComponentAlias *alias = m_core->aliasByName(aliasName);
+ if (!alias) {
+ const QString error = QLatin1String("No required alias found by name: ") + aliasName;
+ qCWarning(lcInstallerInstallLog) << error;
+
+ setUnstable(UnstableError::MissingAlias, error);
+ continue;
+ }
+
+ if (alias->isUnstable()) {
+ const QString error = QLatin1String("Alias requires another alias "
+ "that is marked unstable: ") + aliasName;
+ qCWarning(lcInstallerInstallLog) << error;
+
+ setUnstable(UnstableError::ReferenceToUnstable, error);
+ continue;
+ }
+
+ m_aliases.append(alias);
+ }
+ }
+
+ return m_aliases;
+}
+
+/*!
+ Returns the value specified by \a key, with an optional default value \a defaultValue.
+*/
+QString ComponentAlias::value(const QString &key, const QString &defaultValue) const
+{
+ return m_variables.value(key, defaultValue);
+}
+
+/*!
+ Sets the value specified by \a key to \a value. If the value exists already,
+ it is replaced with the new value.
+*/
+void ComponentAlias::setValue(const QString &key, const QString &value)
+{
+ const QString normalizedValue = m_core->replaceVariables(value);
+ if (m_variables.value(key) == normalizedValue)
+ return;
+
+ m_variables[key] = normalizedValue;
+}
+
+/*!
+ Returns all keys for the component alias values.
+*/
+QStringList ComponentAlias::keys() const
+{
+ return m_variables.keys();
+}
+
+/*!
+ Returns \c true if the alias is marked unstable, \c false otherwise.
+*/
+bool ComponentAlias::isUnstable() const
+{
+ return m_unstable;
+}
+
+/*!
+ Sets the alias unstable with \a error, and a \a message describing the error.
+*/
+void ComponentAlias::setUnstable(UnstableError error, const QString &message)
+{
+ setSelected(false);
+ m_unstable = true;
+
+ const QMetaEnum metaEnum = QMetaEnum::fromType<ComponentAlias::UnstableError>();
+ emit m_core->unstableComponentFound(
+ QLatin1String(metaEnum.valueToKey(error)), message, name());
+}
+
+} // namespace QInstaller
diff --git a/src/libs/installer/componentalias.h b/src/libs/installer/componentalias.h
new file mode 100644
index 000000000..f486b23b9
--- /dev/null
+++ b/src/libs/installer/componentalias.h
@@ -0,0 +1,154 @@
+/**************************************************************************
+**
+** Copyright (C) 2023 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$
+**
+**************************************************************************/
+
+#ifndef COMPONENTALIAS_H
+#define COMPONENTALIAS_H
+
+#include "installer_global.h"
+
+#include <QHash>
+#include <QMetaEnum>
+#include <QString>
+#include <QSet>
+
+namespace QInstaller {
+
+class Component;
+class ComponentAlias;
+class PackageManagerCore;
+
+struct INSTALLER_EXPORT AliasSource
+{
+ enum class SourceFileFormat {
+ Unknown = -1,
+ Xml = 0
+ };
+
+ AliasSource();
+ AliasSource(SourceFileFormat aFormat, const QString &aFilename, int aPriority);
+ AliasSource(const AliasSource &other);
+
+ SourceFileFormat format;
+ QString filename;
+ int priority;
+};
+
+INSTALLER_EXPORT uint qHash(const AliasSource &key, uint seed);
+INSTALLER_EXPORT bool operator==(const AliasSource &lhs, const AliasSource &rhs);
+
+class INSTALLER_EXPORT AliasFinder
+{
+public:
+ using AliasData = QVariantMap;
+ using AliasDataHash = QMultiHash<QString, AliasData>;
+
+ enum struct Resolution {
+ AddNew,
+ KeepExisting,
+ RemoveExisting
+ };
+
+ explicit AliasFinder(PackageManagerCore *core);
+ ~AliasFinder();
+
+ bool run();
+ QList<ComponentAlias *> aliases() const;
+
+ void setAliasSources(const QSet<AliasSource> &sources);
+
+private:
+ void clear();
+ Resolution checkPriorityAndVersion(const AliasData &data) const;
+
+ bool parseXml(AliasSource source);
+
+private:
+ PackageManagerCore *const m_core;
+
+ QSet<AliasSource> m_sources;
+ AliasDataHash m_aliasData;
+ QHash<QString, ComponentAlias *> m_aliases;
+};
+
+class INSTALLER_EXPORT ComponentAlias : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY_MOVE(ComponentAlias)
+
+public:
+ enum UnstableError {
+ ReferenceToUnstable = 0,
+ MissingComponent,
+ UnselectableComponent,
+ MissingAlias,
+ ComponentNameConfict
+ };
+ Q_ENUM(UnstableError)
+
+ ComponentAlias(PackageManagerCore *core);
+ ~ComponentAlias();
+
+ QString name() const;
+ QString displayName() const;
+ QString description() const;
+
+ QString version() const;
+
+ bool isVirtual() const;
+
+ bool isSelected() const;
+ void setSelected(bool selected);
+
+ QList<Component *> components();
+ QList<ComponentAlias *> aliases();
+
+ QString value(const QString &key, const QString &defaultValue = QString()) const;
+ void setValue(const QString &key, const QString &value);
+ QStringList keys() const;
+
+ bool isUnstable() const;
+ void setUnstable(UnstableError error, const QString &message = QString());
+
+private:
+ PackageManagerCore *const m_core;
+
+ QHash<QString, QString> m_variables;
+
+ bool m_selected;
+ bool m_unstable;
+
+ QList<Component *> m_components;
+ QList<ComponentAlias *> m_aliases;
+};
+
+} // namespace QInstaller
+
+Q_DECLARE_METATYPE(QInstaller::ComponentAlias *)
+Q_DECLARE_METATYPE(QInstaller::AliasSource);
+
+#endif // COMPONENTALIAS_H
diff --git a/src/libs/installer/constants.h b/src/libs/installer/constants.h
index b30cd2496..73a230e8e 100644
--- a/src/libs/installer/constants.h
+++ b/src/libs/installer/constants.h
@@ -60,6 +60,9 @@ static const QLatin1String scDisplayName("DisplayName");
static const QLatin1String scTreeName("TreeName");
static const QLatin1String scAutoTreeName("AutoTreeName");
static const QLatin1String scDependencies("Dependencies");
+static const QLatin1String scAlias("Alias");
+static const QLatin1String scRequiresAlias("RequiresAlias");
+static const QLatin1String scRequiresComponent("RequiresComponent");
static const QLatin1String scLocalDependencies("LocalDependencies");
static const QLatin1String scAutoDependOn("AutoDependOn");
static const QLatin1String scNewComponent("NewComponent");
@@ -175,6 +178,7 @@ static const QLatin1String scBanner("Banner");
static const QLatin1String scLogo("Logo");
static const QLatin1String scBackground("Background");
static const QLatin1String scPageListPixmap("PageListPixmap");
+static const QLatin1String scAliasDefinitionsFile("AliasDefinitionsFile");
const char scRelocatable[] = "@RELOCATABLE_PATH@";
static const QStringList scMetaElements = {
@@ -279,6 +283,7 @@ static const QLatin1String scFilterPackagesShort("fp");
static const QLatin1String scFilterPackagesLong("filter-packages");
static const QLatin1String scLocalCachePathShort("cp");
static const QLatin1String scLocalCachePathLong("cache-path");
+static const QLatin1String scTypeLong("type");
// Developer options
static const QLatin1String scScriptShort("s");
diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro
index d25795d78..ff7a0eed2 100644
--- a/src/libs/installer/installer.pro
+++ b/src/libs/installer/installer.pro
@@ -47,6 +47,7 @@ greaterThan(QT_MAJOR_VERSION, 5):QT += core5compat
HEADERS += packagemanagercore.h \
aspectratiolabel.h \
calculatorbase.h \
+ componentalias.h \
componentsortfilterproxymodel.h \
concurrentoperationrunner.h \
genericdatacache.h \
@@ -153,6 +154,7 @@ SOURCES += packagemanagercore.cpp \
archivefactory.cpp \
aspectratiolabel.cpp \
calculatorbase.cpp \
+ componentalias.cpp \
concurrentoperationrunner.cpp \
directoryguard.cpp \
fileguard.cpp \
diff --git a/src/libs/installer/installercalculator.cpp b/src/libs/installer/installercalculator.cpp
index 105e99fd8..4c53824af 100644
--- a/src/libs/installer/installercalculator.cpp
+++ b/src/libs/installer/installercalculator.cpp
@@ -29,6 +29,8 @@
#include "installercalculator.h"
#include "component.h"
+#include "componentalias.h"
+#include "componentmodel.h"
#include "packagemanagercore.h"
#include "settings.h"
#include <globals.h>
@@ -51,6 +53,19 @@ InstallerCalculator::~InstallerCalculator()
{
}
+bool InstallerCalculator::solve()
+{
+ if (!solve(m_core->aliasesMarkedForInstallation()))
+ return false;
+
+ // Subtract components added by aliases
+ QList<Component *> components = m_core->componentsMarkedForInstallation();
+ for (auto *component : qAsConst(m_resolvedComponents))
+ components.removeAll(component);
+
+ return solve(components);
+}
+
QString InstallerCalculator::resolutionText(Component *component) const
{
const Resolution reason = resolutionType(component);
@@ -67,6 +82,9 @@ QString InstallerCalculator::resolutionText(Component *component) const
case Resolution::Selected:
return QCoreApplication::translate("InstallerCalculator",
"Selected components without dependencies:");
+ case Resolution::Alias:
+ return QCoreApplication::translate("InstallerCalculator",
+ "Components selected by alias \"%1\":").arg(referencedComponent(component));
default:
Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid install resolution detected!");
}
@@ -110,6 +128,44 @@ bool InstallerCalculator::solve(const QList<Component *> &components)
return true;
}
+bool InstallerCalculator::solve(const QList<ComponentAlias *> &aliases)
+{
+ if (aliases.isEmpty())
+ return true;
+
+ QList<ComponentAlias *> notAppendedAliases; // Aliases that require other aliases
+ for (auto *alias : aliases) {
+ if (!alias)
+ continue;
+
+ if (m_toInstallComponentAliases.contains(alias->name())) {
+ const QString errorMessage = QCoreApplication::translate("InstallerCalculator",
+ "Recursion detected, component alias \"%1\" already added.").arg(alias->name());
+ qCWarning(QInstaller::lcInstallerInstallLog).noquote() << errorMessage;
+ m_errorString.append(errorMessage);
+
+ Q_ASSERT_X(!m_toInstallComponentAliases.contains(alias->name()), Q_FUNC_INFO,
+ qPrintable(errorMessage));
+
+ return false;
+ }
+
+ if (alias->aliases().isEmpty()) {
+ if (!addComponentsFromAlias(alias))
+ return false;
+ } else {
+ notAppendedAliases.append(alias);
+ }
+ }
+
+ for (auto *alias : qAsConst(notAppendedAliases)) {
+ if (!solveAlias(alias))
+ return false;
+ }
+
+ return true;
+}
+
void InstallerCalculator::addComponentForInstall(Component *component, const QString &version)
{
if (!m_componentsForAutodepencencyCheck.contains(component))
@@ -121,12 +177,40 @@ void InstallerCalculator::addComponentForInstall(Component *component, const QSt
}
}
+bool InstallerCalculator::addComponentsFromAlias(ComponentAlias *alias)
+{
+ QList<Component *> componentsToAdd;
+ for (auto *component : alias->components()) {
+ if (m_toInstallComponentIds.contains(component->name()))
+ continue; // Already added
+
+ componentsToAdd.append(component);
+ // Updates the model, so that we also check the descendant
+ // components when calculating components to install
+ updateCheckState(component, Qt::Checked);
+ insertResolution(component, Resolution::Alias, alias->name());
+ }
+
+ m_toInstallComponentAliases.insert(alias->name());
+ return solve(componentsToAdd);
+}
+
QString InstallerCalculator::recursionError(Component *component) const
{
return QCoreApplication::translate("InstallerCalculator", "Recursion detected, component \"%1\" "
"already added with reason: \"%2\"").arg(component->name(), resolutionText(component));
}
+bool InstallerCalculator::updateCheckState(Component *component, Qt::CheckState state)
+{
+ ComponentModel *currentModel = m_core->isUpdater()
+ ? m_core->updaterComponentModel()
+ : m_core->defaultComponentModel();
+
+ const QModelIndex &idx = currentModel->indexFromComponentName(component->treeName());
+ return currentModel->setData(idx, state, Qt::CheckStateRole);
+}
+
bool InstallerCalculator::solveComponent(Component *component, const QString &version)
{
const QStringList dependenciesList = component->currentDependencies();
@@ -200,6 +284,19 @@ bool InstallerCalculator::solveComponent(Component *component, const QString &ve
return true;
}
+bool InstallerCalculator::solveAlias(ComponentAlias *alias)
+{
+ for (auto *requiredAlias : alias->aliases()) {
+ if (!solveAlias(requiredAlias))
+ return false;
+ }
+
+ if (m_toInstallComponentAliases.contains(alias->name()))
+ return true;
+
+ return addComponentsFromAlias(alias);
+}
+
QSet<Component *> InstallerCalculator::autodependencyComponents()
{
// All regular dependencies are resolved. Now we are looking for auto depend on components.
diff --git a/src/libs/installer/installercalculator.h b/src/libs/installer/installercalculator.h
index 339dbeffd..e542dc664 100644
--- a/src/libs/installer/installercalculator.h
+++ b/src/libs/installer/installercalculator.h
@@ -40,6 +40,7 @@
namespace QInstaller {
class Component;
+class ComponentAlias;
class PackageManagerCore;
class INSTALLER_EXPORT InstallerCalculator : public CalculatorBase
@@ -48,20 +49,28 @@ public:
InstallerCalculator(PackageManagerCore *core, const AutoDependencyHash &autoDependencyComponentHash);
~InstallerCalculator();
+ bool solve();
bool solve(const QList<Component *> &components) override;
+ bool solve(const QList<ComponentAlias *> &aliases);
+
QString resolutionText(Component *component) const override;
private:
bool solveComponent(Component *component, const QString &version = QString()) override;
+ bool solveAlias(ComponentAlias *alias);
void addComponentForInstall(Component *component, const QString &version = QString());
+ bool addComponentsFromAlias(ComponentAlias *alias);
QSet<Component *> autodependencyComponents();
QString recursionError(Component *component) const;
+ bool updateCheckState(Component *component, Qt::CheckState state);
+
private:
QHash<Component*, QSet<Component*> > m_visitedComponents;
QList<const Component*> m_componentsForAutodepencencyCheck;
QSet<QString> m_toInstallComponentIds; //for faster lookups
+ QSet<QString> m_toInstallComponentAliases;
//Helper hash for quicker search for autodependency components
AutoDependencyHash m_autoDependencyComponentHash;
};
diff --git a/src/libs/installer/loggingutils.cpp b/src/libs/installer/loggingutils.cpp
index 42bbc0117..21ad295f9 100644
--- a/src/libs/installer/loggingutils.cpp
+++ b/src/libs/installer/loggingutils.cpp
@@ -29,6 +29,7 @@
#include "loggingutils.h"
#include "component.h"
+#include "componentalias.h"
#include "globals.h"
#include "fileutils.h"
#include "packagemanagercore.h"
@@ -367,6 +368,41 @@ void LoggingHandler::printPackageInformation(const PackagesList &matchedPackages
}
/*!
+ Prints basic or more detailed information about component \a aliases,
+ depending on the current verbosity level.
+*/
+void LoggingHandler::printAliasInformation(const QList<ComponentAlias *> &aliases)
+{
+ QList<ComponentAlias *> sortedAliases = aliases;
+ std::sort(sortedAliases.begin(), sortedAliases.end(),
+ [](const ComponentAlias *lhs, const ComponentAlias *rhs) {
+ return lhs->name() < rhs->name();
+ }
+ );
+
+ QString output;
+ QTextStream stream(&output);
+
+ stream << Qt::endl;
+ for (auto *alias : qAsConst(sortedAliases)) {
+ stream << "Name: " << alias->name() << Qt::endl;
+ stream << "Display name: " << alias->displayName() << Qt::endl;
+ stream << "Description: " << alias->description() << Qt::endl;
+ stream << "Version: " << alias->version() << Qt::endl;
+ if (verboseLevel() == VerbosityLevel::Detailed)
+ stream << "Virtual: " << alias->value(scVirtual) << Qt::endl;
+
+ stream << "Components: " << alias->value(scRequiresComponent) << Qt::endl;
+ stream << "Required aliases: " << alias->value(scRequiresAlias) << Qt::endl;
+
+ if (sortedAliases.indexOf(alias) != (sortedAliases.count() - 1))
+ stream << "========================================" << Qt::endl;
+ }
+
+ std::cout << qPrintable(output);
+}
+
+/*!
\internal
*/
VerboseWriter::VerboseWriter()
diff --git a/src/libs/installer/loggingutils.h b/src/libs/installer/loggingutils.h
index 8bd4217ee..18ff2d2c5 100644
--- a/src/libs/installer/loggingutils.h
+++ b/src/libs/installer/loggingutils.h
@@ -41,6 +41,7 @@
namespace QInstaller {
class Component;
+class ComponentAlias;
class INSTALLER_EXPORT LoggingHandler
{
@@ -67,6 +68,7 @@ public:
void printUpdateInformation(const QList<Component *> &components) const;
void printLocalPackageInformation(const QList<KDUpdater::LocalPackage> &packages) const;
void printPackageInformation(const PackagesList &matchedPackages, const LocalPackagesMap &installedPackages) const;
+ void printAliasInformation(const QList<ComponentAlias *> &aliases);
friend VerbosityLevel &operator++(VerbosityLevel &level, int);
friend VerbosityLevel &operator--(VerbosityLevel &level, int);
diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp
index 977323ec1..62852a682 100644
--- a/src/libs/installer/packagemanagercore.cpp
+++ b/src/libs/installer/packagemanagercore.cpp
@@ -31,6 +31,7 @@
#include "adminauthorization.h"
#include "binarycontent.h"
#include "component.h"
+#include "componentalias.h"
#include "componentmodel.h"
#include "downloadarchivesjob.h"
#include "errors.h"
@@ -1599,6 +1600,7 @@ void PackageManagerCore::networkSettingsChanged()
cancelMetaInfoJob();
d->m_updates = false;
+ d->m_aliases = false;
d->m_repoFetched = false;
d->m_updateSourcesAdded = false;
@@ -2114,6 +2116,15 @@ Component *PackageManagerCore::componentByName(const QString &name) const
}
/*!
+ Searches for a component alias matching \a name and returns it.
+ If no alias matches the name, \c nullptr is returned.
+*/
+ComponentAlias *PackageManagerCore::aliasByName(const QString &name) const
+{
+ return d->m_componentAliases.value(name);
+}
+
+/*!
Searches \a components for a component matching \a name and returns it.
\a name can also contain a version requirement. For example, \c org.qt-project.sdk.qt
returns any component with that name, whereas \c{org.qt-project.sdk.qt->=4.5} requires
@@ -2188,8 +2199,26 @@ QList<Component *> PackageManagerCore::componentsMarkedForInstallation() const
}
/*!
- Determines which components to install based on the current run mode, including dependencies
- and automatic dependencies. Returns \c true on success, \c false otherwise.
+ Returns a list of component aliases that are marked for installation.
+ The list can be empty.
+*/
+QList<ComponentAlias *> PackageManagerCore::aliasesMarkedForInstallation() const
+{
+ if (isUpdater()) // Aliases not supported on update at the moment
+ return QList<ComponentAlias *>();
+
+ QList<ComponentAlias *> markedForInstallation;
+ for (auto *alias : qAsConst(d->m_componentAliases)) {
+ if (alias && alias->isSelected())
+ markedForInstallation.append(alias);
+ }
+
+ return markedForInstallation;
+}
+
+/*!
+ Determines which components to install based on the current run mode, including component aliases,
+ dependencies and automatic dependencies. Returns \c true on success, \c false otherwise.
The aboutCalculateComponentsToInstall() signal is emitted
before the calculation starts, the finishedCalculateComponentsToInstall()
@@ -2203,15 +2232,13 @@ bool PackageManagerCore::calculateComponentsToInstall() const
emit aboutCalculateComponentsToInstall();
d->clearInstallerCalculator();
- const QList<Component*> selectedComponentsToInstall = componentsMarkedForInstallation();
- const bool componentsToInstallCalculated =
- d->installerCalculator()->solve(selectedComponentsToInstall);
+ const bool calculated = d->installerCalculator()->solve();
d->updateComponentInstallActions();
emit finishedCalculateComponentsToInstall();
- return componentsToInstallCalculated;
+ return calculated;
}
/*!
@@ -2562,6 +2589,52 @@ void PackageManagerCore::listAvailablePackages(const QString &regexp, const QHas
LoggingHandler::instance().printPackageInformation(matchedPackages, localInstalledPackages());
}
+/*!
+ Lists available component aliases filtered with \a regexp without GUI. Virtual
+ aliases are not listed unless set visible.
+
+ \sa setVirtualComponentsVisible()
+*/
+void PackageManagerCore::listAvailableAliases(const QString &regexp)
+{
+ setPackageViewer();
+ qCDebug(QInstaller::lcInstallerInstallLog)
+ << "Searching aliases with regular expression:" << regexp;
+
+ ComponentModel *model = defaultComponentModel();
+ Q_UNUSED(model);
+
+ d->fetchMetaInformationFromRepositories();
+ d->addUpdateResourcesFromRepositories();
+
+ QRegularExpression re(regexp);
+ re.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
+ const PackagesList &packages = d->remotePackages();
+ if (!fetchAllPackages(packages, LocalPackagesMap())) {
+ qCWarning(QInstaller::lcInstallerInstallLog)
+ << "There was a problem with loading the package data.";
+ return;
+ }
+
+ QList<ComponentAlias *> matchedAliases;
+ for (auto *alias : qAsConst(d->m_componentAliases)) {
+ if (!alias)
+ continue;
+
+ if (re.match(alias->name()).hasMatch() && !alias->isUnstable()) {
+ if (alias->isVirtual() && !virtualComponentsVisible())
+ continue;
+
+ matchedAliases.append(alias);
+ }
+ }
+
+ if (matchedAliases.isEmpty())
+ qCDebug(QInstaller::lcInstallerInstallLog) << "No matching package aliases found.";
+ else
+ LoggingHandler::instance().printAliasInformation(matchedAliases);
+}
+
bool PackageManagerCore::componentUninstallableFromCommandLine(const QString &componentName)
{
// We will do a recursive check for every child this component has.
@@ -2597,20 +2670,39 @@ 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
+ Tries to set \c Qt::CheckStateRole to \c Qt::Checked for given component \a names in the
+ default component model, and select given aliases in the \c names list.
+
+ Returns \c true if \a names contains at least one component or component alias
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 PackageManagerCore::checkComponentsForInstallation(const QStringList &names, QString &errorMessage)
{
bool installComponentsFound = false;
ComponentModel *model = defaultComponentModel();
- foreach (const QString &name, components) {
+ foreach (const QString &name, names) {
Component *component = componentByName(name);
if (!component) {
- errorMessage.append(tr("Cannot install %1. Component not found.").arg(name) + QLatin1Char('\n'));
+ // No such component, check if we have an alias by the name
+ if (ComponentAlias *alias = aliasByName(name)) {
+ if (alias->isUnstable()) {
+ errorMessage.append(tr("Cannot select alias %1. There was a problem loading this alias, "
+ "so it is marked unstable and cannot be selected.").arg(name) + QLatin1Char('\n'));
+ continue;
+ } else if (alias->isVirtual()) {
+ errorMessage.append(tr("Cannot select %1. Alias is marked virtual, meaning it cannot "
+ "be selected manually.").arg(name) + QLatin1Char('\n'));
+ continue;
+ }
+
+ alias->setSelected(true);
+ installComponentsFound = true;
+ } else {
+ errorMessage.append(tr("Cannot install %1. Component not found.").arg(name) + QLatin1Char('\n'));
+ }
+
continue;
}
const QModelIndex &idx = model->indexFromComponentName(component->treeName());
@@ -3726,6 +3818,14 @@ QString PackageManagerCore::offlineBinaryName() const
}
/*!
+ Add new \a source for looking component aliases.
+*/
+void PackageManagerCore::addAliasSource(const AliasSource &source)
+{
+ d->m_aliasSources.insert(source);
+}
+
+/*!
\sa {installer::setInstaller}{installer.setInstaller}
\sa isInstaller(), setUpdater(), setPackageManager()
*/
@@ -4275,6 +4375,9 @@ bool PackageManagerCore::fetchAllPackages(const PackagesList &remotes, const Loc
d->commitPendingUnstableComponents();
+ if (!d->buildComponentAliases())
+ return false;
+
} catch (const Error &error) {
d->clearAllComponentLists();
d->setStatus(PackageManagerCore::Failure, error.message());
diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h
index f85e5fcae..10df10377 100644
--- a/src/libs/installer/packagemanagercore.h
+++ b/src/libs/installer/packagemanagercore.h
@@ -45,7 +45,9 @@
namespace QInstaller {
+struct AliasSource;
class ComponentModel;
+class ComponentAlias;
class ScriptEngine;
class PackageManagerCorePrivate;
class PackageManagerProxyFactory;
@@ -202,6 +204,8 @@ public:
void setOfflineBinaryName(const QString &name);
QString offlineBinaryName() const;
+ void addAliasSource(const AliasSource &source);
+
Q_INVOKABLE void addUserRepositories(const QStringList &repositories);
Q_INVOKABLE void setTemporaryRepositories(const QStringList &repositories,
bool replace = false, bool compressed = false);
@@ -244,6 +248,8 @@ public:
Q_INVOKABLE QInstaller::Component *componentByName(const QString &identifier) const;
Q_INVOKABLE QList<QInstaller::Component *> components(const QString &regexp = QString()) const;
+ ComponentAlias *aliasByName(const QString &name) const;
+
Q_INVOKABLE bool calculateComponentsToInstall() const;
QList<Component*> orderedComponentsToInstall() const;
@@ -253,6 +259,9 @@ public:
Q_INVOKABLE bool calculateComponentsToUninstall() const;
QList<Component*> componentsToUninstall() const;
+ QList<Component *> componentsMarkedForInstallation() const;
+ QList<ComponentAlias *> aliasesMarkedForInstallation() const;
+
QString componentsToInstallError() const;
QString componentsToUninstallError() const;
QString installReason(Component *component) const;
@@ -264,9 +273,12 @@ public:
ComponentModel *defaultComponentModel() const;
ComponentModel *updaterComponentModel() const;
+
void listInstalledPackages(const QString &regexp = QString());
void listAvailablePackages(const QString &regexp = QString(),
const QHash<QString, QString> &filters = QHash<QString, QString>());
+ void listAvailableAliases(const QString &regexp = QString());
+
PackageManagerCore::Status updateComponentsSilently(const QStringList &componentsToUpdate);
PackageManagerCore::Status installSelectedComponentsSilently(const QStringList& components);
PackageManagerCore::Status installDefaultComponentsSilently();
@@ -449,11 +461,10 @@ private:
QString findDisplayVersion(const QString &componentName, const QHash<QString, QInstaller::Component*> &components,
const QString& versionKey, QHash<QString, bool> &visited);
ComponentModel *componentModel(PackageManagerCore *core, const QString &objectName) const;
- QList<Component *> componentsMarkedForInstallation() const;
bool fetchPackagesTree(const PackagesList &packages, const LocalPackagesMap installedPackages);
bool componentUninstallableFromCommandLine(const QString &componentName);
- bool checkComponentsForInstallation(const QStringList &components, QString &errorMessage);
+ bool checkComponentsForInstallation(const QStringList &names, QString &errorMessage);
private:
PackageManagerCorePrivate *const d;
diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp
index 2bef34bb3..f17eb3c70 100644
--- a/src/libs/installer/packagemanagercore_p.cpp
+++ b/src/libs/installer/packagemanagercore_p.cpp
@@ -45,6 +45,7 @@
#include "qsettingswrapper.h"
#include "installercalculator.h"
#include "uninstallercalculator.h"
+#include "componentalias.h"
#include "componentchecker.h"
#include "globals.h"
#include "binarycreator.h"
@@ -156,6 +157,7 @@ static void deferredRename(const QString &oldName, const QString &newName, bool
PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core)
: m_updateFinder(nullptr)
+ , m_aliasFinder(nullptr)
, m_localPackageHub(std::make_shared<LocalPackageHub>())
, m_status(PackageManagerCore::Unfinished)
, m_needsHardRestart(false)
@@ -173,6 +175,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core)
, m_autoConfirmCommand(false)
, m_core(core)
, m_updates(false)
+ , m_aliases(false)
, m_repoFetched(false)
, m_updateSourcesAdded(false)
, m_magicBinaryMarker(0) // initialize with pseudo marker
@@ -194,6 +197,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core)
PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, qint64 magicInstallerMaker,
const QList<OperationBlob> &performedOperations, const QString &datFileName)
: m_updateFinder(nullptr)
+ , m_aliasFinder(nullptr)
, m_localPackageHub(std::make_shared<LocalPackageHub>())
, m_status(PackageManagerCore::Unfinished)
, m_needsHardRestart(false)
@@ -211,6 +215,7 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q
, m_autoConfirmCommand(false)
, m_core(core)
, m_updates(false)
+ , m_aliases(false)
, m_repoFetched(false)
, m_updateSourcesAdded(false)
, m_magicBinaryMarker(magicInstallerMaker)
@@ -270,6 +275,7 @@ PackageManagerCorePrivate::~PackageManagerCorePrivate()
qDeleteAll(m_performedOperationsCurrentSession);
delete m_updateFinder;
+ delete m_aliasFinder;
delete m_proxyFactory;
delete m_defaultModel;
@@ -430,6 +436,73 @@ bool PackageManagerCorePrivate::buildComponentTree(QHash<QString, Component*> &c
return true;
}
+bool PackageManagerCorePrivate::buildComponentAliases()
+{
+ {
+ const QList<ComponentAlias *> aliasList = componentAliases();
+ if (aliasList.isEmpty())
+ return true;
+
+ for (const auto *alias : aliasList) {
+ // Create a new alias object for package manager core to take ownership of
+ ComponentAlias *newAlias = new ComponentAlias(m_core);
+ for (const QString &key : alias->keys())
+ newAlias->setValue(key, alias->value(key));
+
+ m_componentAliases.insert(alias->name(), newAlias);
+ }
+ }
+
+ // Component check state is changed by alias selection, so store the initial state
+ storeCheckState();
+
+ Graph<QString> aliasGraph;
+ for (auto *alias : qAsConst(m_componentAliases)) {
+ aliasGraph.addNode(alias->name());
+ aliasGraph.addEdges(alias->name(),
+ QInstaller::splitStringWithComma(alias->value(scRequiresAlias)));
+
+ if (!m_core->componentByName(alias->name())) {
+ // Name ok, select for sanity check calculation
+ alias->setSelected(true);
+ } else {
+ alias->setUnstable(ComponentAlias::ComponentNameConfict,
+ tr("Alias declares name that conflicts with an existing component \"%1\"")
+ .arg(alias->name()));
+ }
+ }
+
+ aliasGraph.sort();
+ // Check for cyclic dependency errors
+ if (aliasGraph.hasCycle()) {
+ setStatus(PackageManagerCore::Failure, installerCalculator()->error());
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("Error"),
+ tr("Unresolved component aliases"),
+ tr("Cyclic dependency between aliases \"%1\" and \"%2\" detected.")
+ .arg(aliasGraph.cycle().first, aliasGraph.cycle().second));
+
+ return false;
+ }
+
+ clearInstallerCalculator();
+ // Check for other errors preventing resolving components to install
+ if (!installerCalculator()->solve(m_componentAliases.values())) {
+ setStatus(PackageManagerCore::Failure, installerCalculator()->error());
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("Error"),
+ tr("Unresolved component aliases"), installerCalculator()->error());
+
+ return false;
+ }
+
+ for (auto *alias : qAsConst(m_componentAliases))
+ alias->setSelected(false);
+
+ // Restore original state
+ restoreCheckState();
+
+ return true;
+}
+
template <typename T>
bool PackageManagerCorePrivate::loadComponentScripts(const T &components, const bool postScript)
{
@@ -491,6 +564,9 @@ ScriptEngine *PackageManagerCorePrivate::controlScriptEngine() const
void PackageManagerCorePrivate::clearAllComponentLists()
{
+ qDeleteAll(m_componentAliases);
+ m_componentAliases.clear();
+
QList<QInstaller::Component*> toDelete;
toDelete << m_rootComponents << m_deletedReplacedComponents;
@@ -650,6 +726,10 @@ void PackageManagerCorePrivate::initialize(const QHash<QString, QString> &params
if (isInstaller())
m_packageSources.insert(PackageSource(QUrl(QLatin1String("resource://metadata/")), 1));
+ const QString aliasFilePath = m_core->settings().aliasDefinitionsFile();
+ if (!aliasFilePath.isEmpty())
+ m_aliasSources.insert(AliasSource(AliasSource::SourceFileFormat::Xml, aliasFilePath, -1));
+
m_metadataJob.disconnect();
m_metadataJob.setAutoDelete(false);
m_metadataJob.setPackageManagerCore(m_core);
@@ -2783,6 +2863,25 @@ LocalPackagesMap PackageManagerCorePrivate::localInstalledPackages()
return m_localPackageHub->localPackages();
}
+QList<ComponentAlias *> PackageManagerCorePrivate::componentAliases()
+{
+ if (m_aliases && m_aliasFinder)
+ return m_aliasFinder->aliases();
+
+ m_aliases = false;
+ delete m_aliasFinder;
+
+ m_aliasFinder = new AliasFinder(m_core);
+ m_aliasFinder->setAliasSources(m_aliasSources);
+ if (!m_aliasFinder->run()) {
+ qCDebug(lcDeveloperBuild) << "No component aliases found." << Qt::endl;
+ return QList<ComponentAlias *>();
+ }
+
+ m_aliases = true;
+ return m_aliasFinder->aliases();
+}
+
bool PackageManagerCorePrivate::fetchMetaInformationFromRepositories(DownloadType type)
{
m_updates = false;
diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h
index 986776479..22b3ca9f3 100644
--- a/src/libs/installer/packagemanagercore_p.h
+++ b/src/libs/installer/packagemanagercore_p.h
@@ -54,8 +54,11 @@ using namespace KDUpdater;
namespace QInstaller {
struct BinaryLayout;
+struct AliasSource;
+class AliasFinder;
class ScriptEngine;
class ComponentModel;
+class ComponentAlias;
class InstallerCalculator;
class UninstallerCalculator;
class RemoteFileEngineHandler;
@@ -106,6 +109,7 @@ public:
QString configurationFileName() const;
bool buildComponentTree(QHash<QString, Component*> &components, bool loadScript);
+ bool buildComponentAliases();
template <typename T>
bool loadComponentScripts(const T &components, const bool postScript = false);
@@ -185,8 +189,10 @@ signals:
public:
UpdateFinder *m_updateFinder;
+ AliasFinder *m_aliasFinder;
QSet<PackageSource> m_packageSources;
QSet<PackageSource> m_compressedPackageSources;
+ QSet<AliasSource> m_aliasSources;
std::shared_ptr<LocalPackageHub> m_localPackageHub;
QStringList m_filesForDelayedDeletion;
@@ -214,6 +220,8 @@ public:
QList<QInstaller::Component*> m_updaterComponentsDeps;
QList<QInstaller::Component*> m_updaterDependencyReplacements;
+ QHash<QString, QInstaller::ComponentAlias *> m_componentAliases;
+
OperationList m_ownedOperations;
OperationList m_performedOperationsOld;
OperationList m_performedOperationsCurrentSession;
@@ -258,6 +266,8 @@ private:
PackagesList remotePackages();
LocalPackagesMap localInstalledPackages();
+ QList<ComponentAlias *> componentAliases();
+
bool fetchMetaInformationFromRepositories(DownloadType type = DownloadType::All);
bool addUpdateResourcesFromRepositories(bool compressedRepository = false);
void processFilesForDelayedDeletion();
@@ -281,6 +291,7 @@ private:
TempPathDeleter m_tmpPathDeleter;
bool m_updates;
+ bool m_aliases;
bool m_repoFetched;
bool m_updateSourcesAdded;
qint64 m_magicBinaryMarker;
diff --git a/src/libs/installer/settings.cpp b/src/libs/installer/settings.cpp
index cf571a04c..594ba8fea 100644
--- a/src/libs/installer/settings.cpp
+++ b/src/libs/installer/settings.cpp
@@ -318,7 +318,7 @@ Settings Settings::fromFileAndPrefix(const QString &path, const QString &prefix,
elementList << scName << scVersion << scTitle << scPublisher << scProductUrl
<< scTargetDir << scAdminTargetDir
<< scInstallerApplicationIcon << scInstallerWindowIcon
- << scLogo << scWatermark << scBanner << scBackground << scPageListPixmap
+ << scLogo << scWatermark << scBanner << scBackground << scPageListPixmap << scAliasDefinitionsFile
<< scStartMenuDir << scMaintenanceToolName << scMaintenanceToolIniFile << scMaintenanceToolAlias
<< scRemoveTargetDir << scLocalCacheDir << scPersistentLocalCache
<< scRunProgram << scRunProgramArguments << scRunProgramDescription
@@ -539,6 +539,11 @@ void Settings::setProductImages(const QMap<QString, QVariant> &images)
d->m_data.insert(scProductImages, QVariant::fromValue(images));
}
+QString Settings::aliasDefinitionsFile() const
+{
+ return d->absolutePathFromKey(scAliasDefinitionsFile);
+}
+
QString Settings::installerApplicationIcon() const
{
return d->absolutePathFromKey(scInstallerApplicationIcon, systemIconSuffix());
diff --git a/src/libs/installer/settings.h b/src/libs/installer/settings.h
index f98319110..077646dff 100644
--- a/src/libs/installer/settings.h
+++ b/src/libs/installer/settings.h
@@ -95,6 +95,8 @@ public:
QMap<QString, QVariant> productImages() const;
void setProductImages(const QMap<QString, QVariant> &images);
+ QString aliasDefinitionsFile() const;
+
QString applicationName() const;
QString version() const;
diff --git a/src/sdk/commandlineinterface.cpp b/src/sdk/commandlineinterface.cpp
index afc5c2f42..710fb8a97 100644
--- a/src/sdk/commandlineinterface.cpp
+++ b/src/sdk/commandlineinterface.cpp
@@ -139,7 +139,17 @@ int CommandLineInterface::searchAvailablePackages()
QString regexp;
if (!m_positionalArguments.isEmpty())
regexp = m_positionalArguments.first();
- m_core->listAvailablePackages(regexp, parsePackageFilters());
+
+ bool searchAliases = true;
+ if (m_parser.isSet(CommandLineOptions::scTypeLong)) {
+ searchAliases = (m_parser.value(CommandLineOptions::scTypeLong)
+ != QLatin1String("packages"));
+ }
+ if (searchAliases)
+ m_core->listAvailableAliases(regexp);
+ else
+ m_core->listAvailablePackages(regexp, parsePackageFilters());
+
return EXIT_SUCCESS;
}