Skip to content

Commit

Permalink
Add support for JSON formatted component alias source files
Browse files Browse the repository at this point in the history
The JSON formatted source file follows the same properties as
the officially supported XML declaration file. The undocumented
(private) file format support is added for installers extended
via the ProductKeyCheck API, that can use the JSON format.

Task-number: QTIFW-3175
Change-Id: If1463a1e3796b818feb8682d66e443e09fefcf15
Reviewed-by: Katja Marttila <[email protected]>
  • Loading branch information
artarkia committed Oct 6, 2023
1 parent 7acda96 commit eed88d6
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 19 deletions.
87 changes: 78 additions & 9 deletions src/libs/installer/componentalias.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,24 @@
#include "packagemanagercore.h"
#include "updater.h"

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

namespace QInstaller {

static const QStringList scPossibleElements {
scName,
scDisplayName,
scDescription,
scVersion,
scVirtual,
scRequiredComponents,
scRequiredAliases,
scOptionalComponents,
scOptionalAliases
};

/*!
\inmodule QtInstallerFramework
\class QInstaller::AliasSource
Expand All @@ -50,6 +66,8 @@ namespace QInstaller {
Invalid or unknown file format.
\value Xml
XML file format.
\value Json
JSON file format.
*/

/*!
Expand Down Expand Up @@ -143,6 +161,8 @@ bool AliasFinder::run()
}
if (source.format == AliasSource::SourceFileFormat::Xml)
parseXml(source);
else if (source.format == AliasSource::SourceFileFormat::Json)
parseJson(source);
}

// 2. Create aliases based on priority & version
Expand Down Expand Up @@ -240,15 +260,7 @@ bool AliasFinder::parseXml(AliasSource source)
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 != scRequiredComponents
&& tag2 != scRequiredAliases
&& tag2 != scOptionalComponents
&& tag2 != scOptionalAliases) {
if (!scPossibleElements.contains(tag2)) {
qCWarning(lcInstallerInstallLog) << "Unexpected element name:" << tag2;
continue;
}
Expand All @@ -261,6 +273,63 @@ bool AliasFinder::parseXml(AliasSource source)
return true;
}

/*!
Reads a JSON file specified by \a source, and constructs a variant map of
the data for each alias.
Returns \c true on success, \c false otherwise.
*/
bool AliasFinder::parseJson(AliasSource source)
{
QFile file(source.filename);
if (!file.open(QIODevice::ReadOnly)) {
qCWarning(QInstaller::lcInstallerInstallLog)
<< "Cannot open alias definition for reading:" << file.errorString();
return false;
}

const QByteArray jsonData = file.readAll();
const QJsonDocument doc(QJsonDocument::fromJson(jsonData));
const QJsonObject docJsonObject = doc.object();

const QJsonArray aliases = docJsonObject.value(QLatin1String("alias-packages")).toArray();
for (auto &it : aliases) {
AliasData data;
data.insert(QLatin1String("source"), QVariant::fromValue(source));

QJsonObject aliasObj = it.toObject();
for (const auto &key : aliasObj.keys()) {
if (!scPossibleElements.contains(key)) {
qCWarning(lcInstallerInstallLog) << "Unexpected element name:" << key;
continue;
}

const QJsonValue jsonValue = aliasObj.value(key);
if (key == scRequiredComponents || key == scRequiredAliases
|| key == scOptionalComponents || key == scOptionalAliases) {
const QJsonArray requirements = jsonValue.toArray();
QString requiresString;

for (const auto &it2 : requirements) {
requiresString.append(it2.toString());
if (it2 != requirements.last())
requiresString.append(QLatin1Char(','));
}

data.insert(key, requiresString);
} else if (key == scVirtual) {
data.insert(key, QVariant(jsonValue.toBool()))->toString();
} else {
data.insert(key, jsonValue.toString());
}
}

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.
Expand Down
4 changes: 3 additions & 1 deletion src/libs/installer/componentalias.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ struct INSTALLER_EXPORT AliasSource
{
enum class SourceFileFormat {
Unknown = -1,
Xml = 0
Xml = 0,
Json
};

AliasSource();
Expand Down Expand Up @@ -86,6 +87,7 @@ class INSTALLER_EXPORT AliasFinder
Resolution checkPriorityAndVersion(const AliasData &data) const;

bool parseXml(AliasSource source);
bool parseJson(AliasSource source);

private:
PackageManagerCore *const m_core;
Expand Down
38 changes: 38 additions & 0 deletions tests/auto/installer/componentalias/data/aliases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"alias-packages": [
{
"Description": "Installs component A",
"DisplayName": "Installation A (JSON)",
"Name": "set-A-json",
"RequiredComponents": [
"A"
],
"Version": "1.0.0",
"Virtual": false
},
{
"Description": "Installs component B",
"DisplayName": "Virtual installation B (JSON)",
"Name": "set-B-json",
"RequiredComponents": [
"B"
],
"Version": "1.0.0",
"Virtual": true
},
{
"Description": "Installs all components",
"DisplayName": "Full installation (JSON)",
"Name": "set-full-json",
"RequiredAliases": [
"set-A-json",
"set-B-json"
],
"RequiredComponents": [
"C"
],
"Version": "1.0.0",
"Virtual": false
}
]
}
1 change: 1 addition & 0 deletions tests/auto/installer/componentalias/settings.qrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<RCC>
<qresource prefix="/">
<file>data/repository/Updates.xml</file>
<file>data/aliases.json</file>
<file>data/aliases-priority.xml</file>
<file>data/aliases-versions.xml</file>
<file>data/aliases-optional.xml</file>
Expand Down
24 changes: 15 additions & 9 deletions tests/auto/installer/componentalias/tst_componentalias.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,45 +166,51 @@ private slots:

void testInstallAlias_data()
{
QTest::addColumn<QString>("additionalSource");
QTest::addColumn<AliasSource>("additionalSource");
QTest::addColumn<QStringList>("selectedAliases");
QTest::addColumn<PackageManagerCore::Status>("status");
QTest::addColumn<QStringList>("installedComponents");

QTest::newRow("Simple alias")
<< QString()
<< AliasSource()
<< (QStringList() << "set-A")
<< PackageManagerCore::Success
<< (QStringList() << "A");

QTest::newRow("Alias with dependencies")
<< QString()
<< AliasSource()
<< (QStringList() << "set-full")
<< PackageManagerCore::Success
<< (QStringList() << "A" << "B" << "C" << "C.subcomponent" << "C.subcomponent.subcomponent");

QTest::newRow("Alias with dependencies (JSON source)")
<< AliasSource(AliasSource::SourceFileFormat::Json, ":///data/aliases.json", -1)
<< (QStringList() << "set-full-json")
<< PackageManagerCore::Success
<< (QStringList() << "A" << "B" << "C" << "C.subcomponent" << "C.subcomponent.subcomponent");

QTest::newRow("Alias with optional components (existent and non-existent)")
<< ":///data/aliases-optional.xml"
<< AliasSource(AliasSource::SourceFileFormat::Xml, ":///data/aliases-optional.xml", -1)
<< (QStringList() << "set-A")
<< PackageManagerCore::Success
<< (QStringList() << "A" << "B");

QTest::newRow("Alias with optional aliases (existent and non-existent)")
<< ":///data/aliases-optional.xml"
<< AliasSource(AliasSource::SourceFileFormat::Xml, ":///data/aliases-optional.xml", -1)
<< (QStringList() << "set-full")
<< PackageManagerCore::Success
<< (QStringList() << "A" << "B");

QTest::newRow("Alias with optional broken alias (will not install)")
<< ":///data/aliases-optional.xml"
<< AliasSource(AliasSource::SourceFileFormat::Xml, ":///data/aliases-optional.xml", -1)
<< (QStringList() << "set-optional-broken")
<< PackageManagerCore::Canceled
<< QStringList();
}

void testInstallAlias()
{
QFETCH(QString, additionalSource);
QFETCH(AliasSource, additionalSource);
QFETCH(QStringList, selectedAliases);
QFETCH(PackageManagerCore::Status, status);
QFETCH(QStringList, installedComponents);
Expand All @@ -214,8 +220,8 @@ private slots:

core->setCommandLineInstance(true);

if (!additionalSource.isEmpty())
core->addAliasSource(AliasSource(AliasSource::SourceFileFormat::Xml, additionalSource, -1));
if (!additionalSource.filename.isEmpty())
core->addAliasSource(additionalSource);

QCOMPARE(core->installSelectedComponentsSilently(selectedAliases), status);

Expand Down

0 comments on commit eed88d6

Please sign in to comment.