1 /****************************************************************************
2 **
3 ** Copyright (C) 2021 Ivan Komissarov (abbapoh@gmail.com)
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qbs.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "pkgconfigjs.h"
41 
42 #include <language/scriptengine.h>
43 #include <tools/version.h>
44 
45 #include <QtScript/qscriptengine.h>
46 #include <QtScript/qscriptvalue.h>
47 
48 #include <QtCore/QProcessEnvironment>
49 
50 #include <stdexcept>
51 
52 namespace qbs {
53 namespace Internal {
54 
55 namespace {
56 
convert(const C & c,F && f)57 template<typename C, typename F> QVariantList convert(const C &c, F &&f)
58 {
59     QVariantList result;
60     result.reserve(int(c.size()));
61     std::transform(c.begin(), c.end(), std::back_inserter(result), f);
62     return result;
63 }
64 
variablesMapToMap(const PcPackage::VariablesMap & variables)65 QVariantMap variablesMapToMap(const PcPackage::VariablesMap &variables)
66 {
67     QVariantMap result;
68     for (const auto &item: variables)
69         result.insert(QString::fromStdString(item.first), QString::fromStdString(item.second));
70     return result;
71 }
72 
packageToMap(const PcPackage & package)73 QVariantMap packageToMap(const PcPackage &package)
74 {
75     QVariantMap result;
76     result[QStringLiteral("isBroken")] = false;
77     result[QStringLiteral("filePath")] = QString::fromStdString(package.filePath);
78     result[QStringLiteral("baseFileName")] = QString::fromStdString(package.baseFileName);
79     result[QStringLiteral("name")] = QString::fromStdString(package.name);
80     result[QStringLiteral("version")] = QString::fromStdString(package.version);
81     result[QStringLiteral("description")] = QString::fromStdString(package.description);
82     result[QStringLiteral("url")] = QString::fromStdString(package.url);
83 
84     const auto flagToMap = [](const PcPackage::Flag &flag)
85     {
86         QVariantMap result;
87         const auto value = QString::fromStdString(flag.value);
88         result[QStringLiteral("type")] = QVariant::fromValue(qint32(flag.type));
89         result[QStringLiteral("value")] = value;
90         return result;
91     };
92 
93     const auto requiredVersionToMap = [](const PcPackage::RequiredVersion &version)
94     {
95         using Type = PcPackage::RequiredVersion::ComparisonType;
96         QVariantMap result;
97         result[QStringLiteral("name")] = QString::fromStdString(version.name);
98         const auto versionString = QString::fromStdString(version.version);
99         const auto qbsVersion = Version::fromString(QString::fromStdString(version.version));
100         const auto nextQbsVersion = Version(
101                 qbsVersion.majorVersion(),
102                 qbsVersion.minorVersion(),
103                 qbsVersion.patchLevel() + 1);
104         switch (version.comparison) {
105         case Type::LessThan:
106             result[QStringLiteral("versionBelow")] = versionString;
107             break;
108         case Type::GreaterThan:
109             result[QStringLiteral("versionAtLeast")] = nextQbsVersion.toString();
110             break;
111         case Type::LessThanEqual:
112             result[QStringLiteral("versionBelow")] = nextQbsVersion.toString();
113             break;
114         case Type::GreaterThanEqual:
115             result[QStringLiteral("versionAtLeast")] = versionString;
116             break;
117         case Type::Equal:
118             result[QStringLiteral("version")] = versionString;
119             break;
120         case Type::NotEqual:
121             result[QStringLiteral("versionBelow")] = versionString;
122             result[QStringLiteral("versionAtLeast")] = nextQbsVersion.toString();
123             break;
124         case Type::AlwaysMatch:
125             break;
126         }
127         result[QStringLiteral("comparison")] = QVariant::fromValue(qint32(version.comparison));
128         return result;
129     };
130 
131     result[QStringLiteral("libs")] = convert(package.libs, flagToMap);
132     result[QStringLiteral("libsPrivate")] = convert(package.libsPrivate, flagToMap);
133     result[QStringLiteral("cflags")] = convert(package.cflags, flagToMap);
134     result[QStringLiteral("requires")] = convert(package.requiresPublic, requiredVersionToMap);
135     result[QStringLiteral("requiresPrivate")] =
136             convert(package.requiresPrivate, requiredVersionToMap);
137     result[QStringLiteral("conflicts")] = convert(package.conflicts, requiredVersionToMap);
138     result[QStringLiteral("variables")] = variablesMapToMap(package.variables);
139 
140     return result;
141 };
142 
brokenPackageToMap(const PcBrokenPackage & package)143 QVariantMap brokenPackageToMap(const PcBrokenPackage &package)
144 {
145     QVariantMap result;
146     result[QStringLiteral("isBroken")] = true;
147     result[QStringLiteral("filePath")] = QString::fromStdString(package.filePath);
148     result[QStringLiteral("baseFileName")] = QString::fromStdString(package.baseFileName);
149     result[QStringLiteral("errorText")] = QString::fromStdString(package.errorText);
150     return result;
151 }
152 
packageVariantToMap(const PcPackageVariant & package)153 QVariantMap packageVariantToMap(const PcPackageVariant &package)
154 {
155     return package.visit([](const auto &value) {
156         using T = std::decay_t<decltype(value)>;
157         if constexpr (std::is_same_v<T, PcPackage>)
158             return packageToMap(value);
159         else
160             return brokenPackageToMap(value);
161     });
162 }
163 
envToVariablesMap(const QProcessEnvironment & env)164 PcPackage::VariablesMap envToVariablesMap(const QProcessEnvironment &env)
165 {
166     PcPackage::VariablesMap result;
167     const auto keys = env.keys();
168     for (const auto &key : keys)
169         result[key.toStdString()] = env.value(key).toStdString();
170     return result;
171 }
172 
variablesFromQVariantMap(const QVariantMap & map)173 PcPackage::VariablesMap variablesFromQVariantMap(const QVariantMap &map)
174 {
175     PcPackage::VariablesMap result;
176     for (auto it = map.cbegin(), end = map.cend(); it != end; ++it)
177         result[it.key().toStdString()] = it.value().toString().toStdString();
178     return result;
179 }
180 
stringListToStdVector(const QStringList & list)181 std::vector<std::string> stringListToStdVector(const QStringList &list)
182 {
183     std::vector<std::string> result;
184     result.reserve(list.size());
185     for (const auto &string : list)
186         result.push_back(string.toStdString());
187     return result;
188 }
189 
190 } // namespace
191 
ctor(QScriptContext * context,QScriptEngine * engine)192 QScriptValue PkgConfigJs::ctor(QScriptContext *context, QScriptEngine *engine)
193 {
194     try {
195         PkgConfigJs *e = nullptr;
196         switch (context->argumentCount()) {
197         case 0:
198             e = new PkgConfigJs(context, engine);
199             break;
200         case 1:
201             e = new PkgConfigJs(context, engine, context->argument(0).toVariant().toMap());
202             break;
203 
204         default:
205             return context->throwError(
206                     QStringLiteral("TextFile constructor takes at most three parameters."));
207         }
208 
209         return engine->newQObject(e, QScriptEngine::ScriptOwnership);
210     } catch (const PcException &e) {
211         return context->throwError(QString::fromUtf8(e.what()));
212     }
213 }
214 
PkgConfigJs(QScriptContext * context,QScriptEngine * engine,const QVariantMap & options)215 PkgConfigJs::PkgConfigJs(
216         QScriptContext *context, QScriptEngine *engine, const QVariantMap &options) :
217     m_pkgConfig(std::make_unique<PkgConfig>(
218         convertOptions(static_cast<ScriptEngine *>(engine)->environment(), options)))
219 {
220     Q_UNUSED(context);
221     for (const auto &package : m_pkgConfig->packages()) {
222         m_packages.insert(
223                 QString::fromStdString(package.getBaseFileName()), packageVariantToMap(package));
224     }
225 }
226 
convertOptions(const QProcessEnvironment & env,const QVariantMap & map)227 PkgConfig::Options PkgConfigJs::convertOptions(const QProcessEnvironment &env, const QVariantMap &map)
228 {
229     PkgConfig::Options result;
230     result.libDirs =
231             stringListToStdVector(map.value(QStringLiteral("libDirs")).toStringList());
232     result.extraPaths =
233             stringListToStdVector(map.value(QStringLiteral("extraPaths")).toStringList());
234     result.sysroot = map.value(QStringLiteral("sysroot")).toString().toStdString();
235     result.topBuildDir = map.value(QStringLiteral("topBuildDir")).toString().toStdString();
236     result.allowSystemLibraryPaths =
237             map.value(QStringLiteral("allowSystemLibraryPaths"), false).toBool();
238     const auto systemLibraryPaths = map.value(QStringLiteral("systemLibraryPaths")).toStringList();
239     result.systemLibraryPaths.reserve(systemLibraryPaths.size());
240     std::transform(
241                 systemLibraryPaths.begin(),
242                 systemLibraryPaths.end(),
243                 std::back_inserter(result.systemLibraryPaths),
244                 [](const QString &str){ return str.toStdString(); });
245     result.disableUninstalled = map.value(QStringLiteral("disableUninstalled"), true).toBool();
246     result.staticMode = map.value(QStringLiteral("staticMode"), false).toBool();
247     result.mergeDependencies = map.value(QStringLiteral("mergeDependencies"), true).toBool();
248     result.globalVariables =
249             variablesFromQVariantMap(map.value(QStringLiteral("globalVariables")).toMap());
250     result.systemVariables = envToVariablesMap(env);
251 
252     return result;
253 }
254 
255 } // namespace Internal
256 } // namespace qbs
257 
initializeJsExtensionPkgConfig(QScriptValue extensionObject)258 void initializeJsExtensionPkgConfig(QScriptValue extensionObject)
259 {
260     using namespace qbs::Internal;
261     QScriptEngine *engine = extensionObject.engine();
262     QScriptValue obj = engine->newQMetaObject(
263                 &PkgConfigJs::staticMetaObject, engine->newFunction(&PkgConfigJs::ctor));
264     extensionObject.setProperty(QStringLiteral("PkgConfig"), obj);
265 }
266 
267 Q_DECLARE_METATYPE(qbs::Internal::PkgConfigJs *)
268