1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the tools applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "scanner.h"
30#include "logging.h"
31
32#include <QtCore/qdir.h>
33#include <QtCore/qjsonarray.h>
34#include <QtCore/qjsondocument.h>
35#include <QtCore/qjsonobject.h>
36#include <QtCore/qregexp.h>
37#include <QtCore/qtextstream.h>
38#include <QtCore/qvariant.h>
39
40#include <iostream>
41
42namespace Scanner {
43
44static void missingPropertyWarning(const QString &filePath, const QString &property)
45{
46 std::cerr << qPrintable(tr("File %1: Missing mandatory property '%2'.").arg(
47 QDir::toNativeSeparators(filePath), property)) << std::endl;
48}
49
50static void validatePackage(Package &p, const QString &filePath, LogLevel logLevel)
51{
52 if (p.qtParts.isEmpty())
53 p.qtParts << QStringLiteral("libs");
54
55 if (logLevel != SilentLog) {
56 if (p.name.isEmpty()) {
57 if (p.id.startsWith(s: QLatin1String("chromium-"))) // Ignore invalid README.chromium files
58 return;
59
60 missingPropertyWarning(filePath, QStringLiteral("Name"));
61 }
62
63 if (p.id.isEmpty())
64 missingPropertyWarning(filePath, QStringLiteral("Id"));
65 if (p.license.isEmpty())
66 missingPropertyWarning(filePath, QStringLiteral("License"));
67
68 for (const QString &part : qAsConst(t&: p.qtParts)) {
69 if (part != QLatin1String("examples")
70 && part != QLatin1String("tests")
71 && part != QLatin1String("tools")
72 && part != QLatin1String("libs")
73 && logLevel != SilentLog) {
74 std::cerr << qPrintable(tr("File %1: Property 'QtPart' contains unknown element "
75 "'%2'. Valid entries are 'examples', 'tests', 'tools' "
76 "and 'libs'.").arg(
77 QDir::toNativeSeparators(filePath), part))
78 << std::endl;
79 }
80 }
81 }
82}
83
84// Transforms a JSON object into a Package object
85static Package readPackage(const QJsonObject &object, const QString &filePath, LogLevel logLevel)
86{
87 Package p;
88 const QString directory = QFileInfo(filePath).absolutePath();
89 p.path = directory;
90
91 for (auto iter = object.constBegin(); iter != object.constEnd(); ++iter) {
92 const QString key = iter.key();
93
94 if (!iter.value().isString() && key != QLatin1String("QtParts")) {
95 if (logLevel != SilentLog)
96 std::cerr << qPrintable(tr("File %1: Expected JSON string as value of %2.").arg(
97 QDir::toNativeSeparators(filePath), key)) << std::endl;
98 continue;
99 }
100 const QString value = iter.value().toString();
101 if (key == QLatin1String("Name")) {
102 p.name = value;
103 } else if (key == QLatin1String("Path")) {
104 p.path = QDir(directory).absoluteFilePath(fileName: value);
105 } else if (key == QLatin1String("Files")) {
106 p.files = value.split(sep: QRegExp(QStringLiteral("\\s")), behavior: Qt::SkipEmptyParts);
107 } else if (key == QLatin1String("Id")) {
108 p.id = value;
109 } else if (key == QLatin1String("Homepage")) {
110 p.homepage = value;
111 } else if (key == QLatin1String("Version")) {
112 p.version = value;
113 } else if (key == QLatin1String("DownloadLocation")) {
114 p.downloadLocation = value;
115 } else if (key == QLatin1String("License")) {
116 p.license = value;
117 } else if (key == QLatin1String("LicenseId")) {
118 p.licenseId = value;
119 } else if (key == QLatin1String("LicenseFile")) {
120 p.licenseFile = QDir(directory).absoluteFilePath(fileName: value);
121 } else if (key == QLatin1String("Copyright")) {
122 p.copyright = value;
123 } else if (key == QLatin1String("PackageComment")) {
124 p.packageComment = value;
125 } else if (key == QLatin1String("QDocModule")) {
126 p.qdocModule = value;
127 } else if (key == QLatin1String("Description")) {
128 p.description = value;
129 } else if (key == QLatin1String("QtUsage")) {
130 p.qtUsage = value;
131 } else if (key == QLatin1String("QtParts")) {
132 const QVariantList variantList = iter.value().toArray().toVariantList();
133 for (const QVariant &v: variantList) {
134 if (v.type() != QVariant::String && logLevel != SilentLog) {
135 std::cerr << qPrintable(tr("File %1: Expected JSON string in array of %2.").arg(
136 QDir::toNativeSeparators(filePath), key))
137 << std::endl;
138 }
139 p.qtParts.append(t: v.toString());
140 }
141 } else {
142 if (logLevel != SilentLog)
143 std::cerr << qPrintable(tr("File %1: Unknown key %2.").arg(
144 QDir::toNativeSeparators(filePath), key)) << std::endl;
145 }
146 }
147
148 validatePackage(p, filePath, logLevel);
149
150 return p;
151}
152
153// Parses a package's details from a README.chromium file
154static Package parseChromiumFile(QFile &file, const QString &filePath, LogLevel logLevel)
155{
156 const QString directory = QFileInfo(filePath).absolutePath();
157
158 // Parse the fields in the file
159 QHash<QString, QString> fields;
160
161 QTextStream in(&file);
162 while (!in.atEnd()) {
163 QString line = in.readLine().trimmed();
164 QStringList parts = line.split(QStringLiteral(":"));
165
166 if (parts.count() < 2)
167 continue;
168
169 QString key = parts.at(i: 0);
170 parts.removeFirst();
171 QString value = parts.join(sep: QString()).trimmed();
172
173 fields[key] = value;
174
175 if (line == QLatin1String("Description:")) { // special field : should handle multi-lines values
176 while (!in.atEnd()) {
177 QString line = in.readLine().trimmed();
178
179 if (line.startsWith(s: QLatin1String("Local Modifications:"))) // Don't include this part
180 break;
181
182 fields[key] += line + QStringLiteral("\n");
183 }
184
185 break;
186 }
187 }
188
189 // Construct the Package object
190 Package p;
191
192 QString shortName = fields.contains(akey: QLatin1String("Short Name"))
193 ? fields[QLatin1String("Short Name")]
194 : fields[QLatin1String("Name")];
195 QString version = fields[QStringLiteral("Version")];
196
197 p.id = QStringLiteral("chromium-") + shortName.toLower().replace(c: QChar::Space, QStringLiteral("-"));
198 p.name = fields[QStringLiteral("Name")];
199 if (version != QLatin1Char('0')) // "0" : not applicable
200 p.version = version;
201 p.license = fields[QStringLiteral("License")];
202 p.homepage = fields[QStringLiteral("URL")];
203 p.qdocModule = QStringLiteral("qtwebengine");
204 p.qtUsage = QStringLiteral("Used in Qt WebEngine");
205 p.description = fields[QStringLiteral("Description")].trimmed();
206 p.path = directory;
207
208 QString licenseFile = fields[QStringLiteral("License File")];
209 if (licenseFile != QString() && licenseFile != QLatin1String("NOT_SHIPPED")) {
210 p.licenseFile = QDir(directory).absoluteFilePath(fileName: licenseFile);
211 } else {
212 // Look for a LICENSE or COPYING file as a fallback
213 QDir dir = directory;
214
215 dir.setNameFilters({ QStringLiteral("LICENSE"), QStringLiteral("COPYING") });
216 dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
217
218 const QFileInfoList entries = dir.entryInfoList();
219 if (!entries.empty())
220 p.licenseFile = entries.at(i: 0).absoluteFilePath();
221 }
222
223 validatePackage(p, filePath, logLevel);
224
225 return p;
226}
227
228QVector<Package> readFile(const QString &filePath, LogLevel logLevel)
229{
230 QVector<Package> packages;
231
232 if (logLevel == VerboseLog) {
233 std::cerr << qPrintable(tr("Reading file %1...").arg(
234 QDir::toNativeSeparators(filePath))) << std::endl;
235 }
236 QFile file(filePath);
237 if (!file.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
238 if (logLevel != SilentLog)
239 std::cerr << qPrintable(tr("Could not open file %1.").arg(
240 QDir::toNativeSeparators(file.fileName()))) << std::endl;
241 return QVector<Package>();
242 }
243
244 if (filePath.endsWith(s: QLatin1String(".json"))) {
245 QJsonParseError jsonParseError;
246 const QJsonDocument document = QJsonDocument::fromJson(json: file.readAll(), error: &jsonParseError);
247 if (document.isNull()) {
248 if (logLevel != SilentLog)
249 std::cerr << qPrintable(tr("Could not parse file %1: %2").arg(
250 QDir::toNativeSeparators(file.fileName()),
251 jsonParseError.errorString()))
252 << std::endl;
253 return QVector<Package>();
254 }
255
256 if (document.isObject()) {
257 packages << readPackage(object: document.object(), filePath: file.fileName(), logLevel);
258 } else if (document.isArray()) {
259 QJsonArray array = document.array();
260 for (int i = 0, size = array.size(); i < size; ++i) {
261 QJsonValue value = array.at(i);
262 if (value.isObject()) {
263 packages << readPackage(object: value.toObject(), filePath: file.fileName(), logLevel);
264 } else {
265 if (logLevel != SilentLog)
266 std::cerr << qPrintable(tr("File %1: Expecting JSON object in array.")
267 .arg(QDir::toNativeSeparators(file.fileName())))
268 << std::endl;
269 }
270 }
271 } else {
272 if (logLevel != SilentLog)
273 std::cerr << qPrintable(tr("File %1: Expecting JSON object in array.").arg(
274 QDir::toNativeSeparators(file.fileName()))) << std::endl;
275 }
276 } else if (filePath.endsWith(s: QLatin1String(".chromium"))) {
277 Package chromiumPackage = parseChromiumFile(file, filePath, logLevel);
278 if (!chromiumPackage.name.isEmpty()) // Skip invalid README.chromium files
279 packages << chromiumPackage;
280 } else {
281 if (logLevel != SilentLog)
282 std::cerr << qPrintable(tr("File %1: Unsupported file type.")
283 .arg(QDir::toNativeSeparators(file.fileName())))
284 << std::endl;
285 }
286
287 return packages;
288}
289
290QVector<Package> scanDirectory(const QString &directory, InputFormats inputFormats, LogLevel logLevel)
291{
292 QDir dir(directory);
293 QVector<Package> packages;
294
295 QStringList nameFilters = QStringList();
296 if (inputFormats & InputFormat::QtAttributions)
297 nameFilters << QStringLiteral("qt_attribution.json");
298 if (inputFormats & InputFormat::ChromiumAttributions)
299 nameFilters << QStringLiteral("README.chromium");
300 if (qEnvironmentVariableIsSet(varName: "QT_ATTRIBUTIONSSCANNER_TEST")) {
301 nameFilters
302 << QStringLiteral("qt_attribution_test.json")
303 << QStringLiteral("README_test.chromium");
304 }
305
306 dir.setNameFilters(nameFilters);
307 dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Files);
308
309 const QFileInfoList entries = dir.entryInfoList();
310 for (const QFileInfo &info : entries) {
311 if (info.isDir()) {
312 packages += scanDirectory(directory: info.filePath(), inputFormats, logLevel);
313 } else {
314 packages += readFile(filePath: info.filePath(), logLevel);
315 }
316 }
317
318 return packages;
319}
320
321} // namespace Scanner
322

source code of qttools/src/qtattributionsscanner/scanner.cpp