Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
scanner.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "scanner.h"
5#include "logging.h"
6
7#include <QtCore/qdir.h>
8#include <QtCore/qhash.h>
9#include <QtCore/qjsonarray.h>
10#include <QtCore/qjsondocument.h>
11#include <QtCore/qjsonobject.h>
12#include <QtCore/qtextstream.h>
13#include <QtCore/qvariant.h>
14
15#include <iostream>
16
17using namespace Qt::Literals::StringLiterals;
18
19namespace Scanner {
20
21static void missingPropertyWarning(const QString &filePath, const QString &property)
22{
23 std::cerr << qPrintable(tr("File %1: Missing mandatory property '%2'.").arg(
24 QDir::toNativeSeparators(filePath), property)) << std::endl;
25}
26
27bool validatePackage(Package &p, Checks checks, LogLevel logLevel)
28{
29 const auto &filePath = p.filePath;
30 bool validPackage = true;
31
32 if (filePath.isEmpty()) {
33 std::cerr << qPrintable(tr("The origin file of package '%1' was not recorded.")
34 .arg(p.id));
35 return false;
36 }
37
38 if (p.qtParts.isEmpty())
39 p.qtParts << u"libs"_s;
40
41 if (p.name.isEmpty()) {
42 if (p.id.startsWith("chromium-"_L1)) // Ignore invalid README.chromium files
43 return false;
44
45 if (logLevel != SilentLog)
46 missingPropertyWarning(filePath, u"Name"_s);
47 validPackage = false;
48 }
49
50 if (p.id.isEmpty()) {
51 if (logLevel != SilentLog)
52 missingPropertyWarning(filePath, u"Id"_s);
53 validPackage = false;
54 } else if (!p.id.isLower() || p.id.contains(' '_L1)) {
55 if (logLevel != SilentLog)
56 std::cerr << qPrintable(tr("File %1: Value of 'Id' must be in lowercase and without spaces.")
57 .arg(QDir::toNativeSeparators(filePath))) << std::endl;
58 validPackage = false;
59 }
60
61 if (p.license.isEmpty()) {
62 if (logLevel != SilentLog)
63 missingPropertyWarning(filePath, u"License"_s);
64 validPackage = false;
65 }
66
67 if (!p.copyright.isEmpty() && !p.copyrightFile.isEmpty()) {
68 if (logLevel != SilentLog) {
69 std::cerr << qPrintable(tr("File %1: Properties 'Copyright' and 'CopyrightFile' are "
70 "mutually exclusive.")
71 .arg(QDir::toNativeSeparators(filePath)))
72 << std::endl;
73 }
74 validPackage = false;
75 }
76
77 if (p.securityCritical && p.downloadLocation.isEmpty()) {
78 if (logLevel != SilentLog)
79 missingPropertyWarning(filePath, u"DownloadLocation"_s);
80 validPackage = false;
81 }
82
83 for (const QString &part : std::as_const(p.qtParts)) {
84 if (part != "examples"_L1 && part != "tests"_L1
85 && part != "tools"_L1 && part != "libs"_L1) {
86
87 if (logLevel != SilentLog) {
88 std::cerr << qPrintable(tr("File %1: Property 'QtPart' contains unknown element "
89 "'%2'. Valid entries are 'examples', 'tests', 'tools' "
90 "and 'libs'.").arg(
91 QDir::toNativeSeparators(filePath), part))
92 << std::endl;
93 }
94 validPackage = false;
95 }
96 }
97
98 if (p.origin == Package::Origin::Provisioned && !p.files.isEmpty()) {
99 std::cerr << qPrintable(tr("File %1: Do not set property 'Files' if property "
100 "'Origin' is set to 'Provisioned'.")
101 .arg(QDir::toNativeSeparators(filePath)))
102 << std::endl;
103
104 validPackage = false;
105 }
106
107 if (!(checks & Check::Paths))
108 return validPackage;
109
110 const QDir dir = p.path;
111 if (!dir.exists()) {
112 std::cerr << qPrintable(
113 tr("File %1: Directory '%2' does not exist.")
114 .arg(QDir::toNativeSeparators(filePath), QDir::toNativeSeparators(p.path)))
115 << std::endl;
116 validPackage = false;
117 } else {
118 for (const QString &file : std::as_const(p.files)) {
119 if (!dir.exists(file)) {
120 if (logLevel != SilentLog) {
121 std::cerr << qPrintable(
122 tr("File %1: Path '%2' does not exist in directory '%3'.")
123 .arg(QDir::toNativeSeparators(filePath),
124 QDir::toNativeSeparators(file),
125 QDir::toNativeSeparators(p.path)))
126 << std::endl;
127 }
128 validPackage = false;
129 }
130 }
131 }
132
133 return validPackage;
134}
135
136static std::optional<QStringList> toStringList(const QJsonValue &value)
137{
138 if (!value.isArray())
139 return std::nullopt;
140 QStringList result;
141 for (const auto &iter : value.toArray()) {
142 if (iter.type() != QJsonValue::String)
143 return std::nullopt;
144 result.push_back(iter.toString());
145 }
146 return result;
147}
148
149static std::optional<QString> arrayToMultiLineString(const QJsonValue &value)
150{
151 if (!value.isArray())
152 return std::nullopt;
153 QString result;
154 for (const auto &iter : value.toArray()) {
155 if (iter.type() != QJsonValue::String)
156 return std::nullopt;
157 result.append(iter.toString());
158 result.append(QLatin1StringView("\n"));
159 }
160 return result;
161}
162
163// Extracts SPDX license ids from a SPDX license expression.
164// For "(BSD-3-Clause AND BeerWare)" this function returns { "BSD-3-Clause", "BeerWare" }.
166{
167 const QStringList spdxOperators = {
168 u"AND"_s,
169 u"OR"_s,
170 u"WITH"_s
171 };
172
173 // Replace parentheses with spaces. We're not interested in grouping.
174 const QRegularExpression parensRegex(u"[()]"_s);
175 expression.replace(parensRegex, u" "_s);
176
177 // Split the string at space boundaries to extract tokens.
178 QStringList result;
179 for (const QString &token : expression.split(QLatin1Char(' '), Qt::SkipEmptyParts)) {
180 if (spdxOperators.contains(token))
181 continue;
182
183 // Remove the unary + operator, if present.
184 if (token.endsWith(QLatin1Char('+')))
185 result.append(token.mid(0, token.size() - 1));
186 else
187 result.append(token);
188 }
189 return result;
190}
191
192// Starting at packageDir, look for a LICENSES subdirectory in the directory hierarchy upwards.
193// Return a default-constructed QString if the directory was not found.
194static QString locateLicensesDir(const QString &packageDir)
195{
196 static const QString licensesSubDir = u"LICENSES"_s;
197 QDir dir(packageDir);
198 while (true) {
199 if (!dir.exists())
200 break;
201 if (dir.cd(licensesSubDir))
202 return dir.path();
203 if (dir.isRoot() || !dir.cdUp())
204 break;
205 }
206 return {};
207}
208
209// Locates the license files that belong to the licenses mentioned in LicenseId and stores them in
210// the specified package object.
212{
213 const QString licensesDirPath = locateLicensesDir(p.path);
214 const QStringList licenseIds = extractLicenseIdsFromSPDXExpression(p.licenseId);
215
216 bool success = true;
217 QDir licensesDir(licensesDirPath);
218 QDir licensesDirLocal = p.path;
219 for (const QString &id : licenseIds) {
220 QString fileName = id + u".txt";
221 QString fileNameLocal = u"LICENSE." + id + u".txt";
222
223 if (licensesDirLocal.exists(fileNameLocal)) {
224 p.licenseFiles.append(licensesDirLocal.filePath(fileNameLocal));
225 } else if (licensesDir.exists(fileName)) {
226 p.licenseFiles.append(licensesDir.filePath(fileName));
227 } else {
228 std::cerr << qPrintable(tr("Missing expected license file:")) << std::endl;
229 std::cerr << qPrintable(QDir::toNativeSeparators(licensesDirLocal.filePath(fileNameLocal)))
230 << std::endl;
231 if (!licensesDirPath.isEmpty()) {
232 std::cerr << qPrintable(tr("or\n %1").arg(
233 QDir::toNativeSeparators(licensesDir.filePath(fileName))))
234 << std::endl;
235 }
236 success = false;
237 }
238 }
239
240 return success;
241}
242
243// Tries to interpret a json value either as a string or an array of strings, and assigns the
244// result to outList. Returns true on success, false on failure. On failure, it also conditionally
245// prints an error.
246static bool handleStringOrStringArrayJsonKey(QStringList &outList, const QString &key,
247 QJsonValueConstRef jsonValue, const QString &filePath,
248 LogLevel logLevel)
249{
250 if (jsonValue.isArray()) {
251 auto maybeStringList = toStringList(jsonValue);
252 if (maybeStringList)
253 outList = maybeStringList.value();
254 } else if (jsonValue.isString()) {
255 outList.append(jsonValue.toString());
256 } else {
257 if (logLevel != SilentLog) {
258 std::cerr << qPrintable(tr("File %1: Expected JSON array of strings or "
259 "string as value of %2.").arg(
260 QDir::toNativeSeparators(filePath), key))
261 << std::endl;
262 }
263 return false;
264 }
265 return true;
266}
267
268// Transforms a JSON object into a Package object
269static std::optional<Package> readPackage(const QJsonObject &object, const QString &filePath,
270 LogLevel logLevel)
271{
272 Package p;
273 bool validPackage = true;
274 const QString directory = QFileInfo(filePath).absolutePath();
275 p.filePath = QFileInfo(filePath).absoluteFilePath();
276 p.path = directory;
277
278 for (auto iter = object.constBegin(); iter != object.constEnd(); ++iter) {
279 const QString key = iter.key();
280
281 if (!iter.value().isString() && key != "QtParts"_L1 && key != "SecurityCritical"_L1
282 && key != "Files"_L1 && key != "LicenseFiles"_L1 && key != "Comment"_L1
283 && key != "Copyright"_L1 && key != "CPE"_L1 && key != "PURL"_L1) {
284 if (logLevel != SilentLog)
285 std::cerr << qPrintable(tr("File %1: Expected JSON string as value of %2.").arg(
286 QDir::toNativeSeparators(filePath), key)) << std::endl;
287 validPackage = false;
288 continue;
289 }
290 const QString value = iter.value().toString();
291 if (key == "Name"_L1) {
292 p.name = value;
293 } else if (key == "Path"_L1) {
294 p.path = QDir(directory).absoluteFilePath(value);
295 } else if (key == "Files"_L1) {
296 QJsonValueConstRef jsonValue = iter.value();
297 if (jsonValue.isArray()) {
298 auto maybeStringList = toStringList(jsonValue);
299 if (maybeStringList)
300 p.files = maybeStringList.value();
301 } else if (jsonValue.isString()) {
302 // Legacy format: multiple values separated by space in one string.
303 p.files = value.simplified().split(QLatin1Char(' '), Qt::SkipEmptyParts);
304 } else {
305 if (logLevel != SilentLog) {
306 std::cerr << qPrintable(tr("File %1: Expected JSON array of strings as value "
307 "of Files.").arg(QDir::toNativeSeparators(filePath)))
308 << std::endl;
309 validPackage = false;
310 continue;
311 }
312 }
313 } else if (key == "Comment"_L1) {
314 // Accepted purely to record details of potential interest doing
315 // updates in future. Value is an arbitrary object. Any number of
316 // Comment entries may be present: JSON doesn't require names to be
317 // unique, albeit some linters may kvetch.
318 } else if (key == "Id"_L1) {
319 p.id = value;
320 } else if (key == "Origin"_L1) {
321 if (value == "InSource"_L1) {
322 p.origin = Package::Origin::InSource;
323 } else if (value == "Provisioned"_L1) {
325 } else {
326 std::cerr << qPrintable(tr("File %1: Expected either 'InSource' or 'Provisioned'"
327 " as value of 'Origin'.")
328 .arg(QDir::toNativeSeparators(filePath)))
329 << std::endl;
330 validPackage = false;
331 continue;
332 }
333 } else if (key == "Homepage"_L1) {
334 p.homepage = value;
335 } else if (key == "Version"_L1) {
336 p.version = value;
337 } else if (key == "DownloadLocation"_L1) {
338 p.downloadLocation = value;
339 } else if (key == "License"_L1) {
340 p.license = value;
341 } else if (key == "LicenseId"_L1) {
342 p.licenseId = value;
343 } else if (key == "LicenseFile"_L1) {
344 p.licenseFiles = QStringList(QDir(directory).absoluteFilePath(value));
345 } else if (key == "LicenseFiles"_L1) {
346 auto strings = toStringList(iter.value());
347 if (!strings) {
348 if (logLevel != SilentLog)
349 std::cerr << qPrintable(tr("File %1: Expected JSON array of strings in %2.")
350 .arg(QDir::toNativeSeparators(filePath), key))
351 << std::endl;
352 validPackage = false;
353 continue;
354 }
355 const QDir dir(directory);
356 for (const auto &iter : std::as_const(strings.value()))
357 p.licenseFiles.push_back(dir.absoluteFilePath(iter));
358 } else if (key == "Copyright"_L1) {
359 QJsonValueConstRef jsonValue = iter.value();
360 if (jsonValue.isArray()) {
361 // Array joined with new lines
362 auto maybeString = arrayToMultiLineString(jsonValue);
363 if (maybeString)
364 p.copyright = maybeString.value();
365 } else if (jsonValue.isString()) {
366 // Legacy format: multiple values separated by space in one string.
367 p.copyright = value;
368 } else {
369 if (logLevel != SilentLog) {
370 std::cerr << qPrintable(tr("File %1: Expected JSON array of strings or "
371 "string as value of %2.").arg(
372 QDir::toNativeSeparators(filePath), key)) << std::endl;
373 validPackage = false;
374 continue;
375 }
376 }
377 } else if (key == "CPE"_L1) {
378 const QJsonValueConstRef jsonValue = iter.value();
379 if (!handleStringOrStringArrayJsonKey(p.cpeList, key, jsonValue, filePath, logLevel)) {
380 validPackage = false;
381 continue;
382 }
383 } else if (key == "PURL"_L1) {
384 const QJsonValueConstRef jsonValue = iter.value();
385 if (!handleStringOrStringArrayJsonKey(p.purlList, key, jsonValue, filePath, logLevel)) {
386 validPackage = false;
387 continue;
388 }
389 } else if (key == "CopyrightFile"_L1) {
390 p.copyrightFile = QDir(directory).absoluteFilePath(value);
391 } else if (key == "PackageComment"_L1) {
392 p.packageComment = value;
393 } else if (key == "QDocModule"_L1) {
394 p.qdocModule = value;
395 } else if (key == "Description"_L1) {
396 p.description = value;
397 } else if (key == "QtUsage"_L1) {
398 p.qtUsage = value;
399 } else if (key == "SecurityCritical"_L1) {
400 if (!iter.value().isBool()) {
401 std::cerr << qPrintable(tr("File %1: Expected JSON boolean in %2.")
402 .arg(QDir::toNativeSeparators(filePath), key))
403 << std::endl;
404 validPackage = false;
405 continue;
406 }
407 p.securityCritical = iter.value().toBool();
408 } else if (key == "QtParts"_L1) {
409 auto parts = toStringList(iter.value());
410 if (!parts) {
411 if (logLevel != SilentLog) {
412 std::cerr << qPrintable(tr("File %1: Expected JSON array of strings in %2.")
413 .arg(QDir::toNativeSeparators(filePath), key))
414 << std::endl;
415 }
416 validPackage = false;
417 continue;
418 }
419 p.qtParts = parts.value();
420 } else {
421 if (logLevel != SilentLog) {
422 std::cerr << qPrintable(tr("File %1: Unknown key %2.").arg(
423 QDir::toNativeSeparators(filePath), key)) << std::endl;
424 }
425 validPackage = false;
426 }
427 }
428
429 // Replace $<VERSION> and $<VERSION_DASHED> in string values
430 {
431 const QString versionVar = u"$<VERSION>"_s;
432 const QString versionDashedVar = u"$<VERSION_DASHED>"_s;
433 auto replaceInString = [&](QString &s) {
434 if (s.contains(versionVar) || s.contains(versionDashedVar)) {
435 if (p.version.isEmpty()) {
436 if (logLevel != SilentLog) {
437 std::cerr << qPrintable(
438 tr("File %1: $<VERSION> used but 'Version' is not set.")
439 .arg(QDir::toNativeSeparators(filePath)))
440 << std::endl;
441 }
442 validPackage = false;
443 return;
444 }
445 s.replace(versionVar, p.version);
446 s.replace(versionDashedVar,
447 QString(p.version).replace(u'.', u'-'));
448 }
449 };
450 auto replaceInList = [&](QStringList &list) {
451 for (QString &s : list)
452 replaceInString(s);
453 };
454 replaceInString(p.name);
455 replaceInString(p.homepage);
456 replaceInString(p.downloadLocation);
457 replaceInString(p.description);
458 replaceInString(p.qtUsage);
459 replaceInString(p.packageComment);
460 replaceInList(p.cpeList);
461 replaceInList(p.purlList);
462 }
463
464 if (!p.copyrightFile.isEmpty()) {
465 QFile file(p.copyrightFile);
466 if (!file.open(QIODevice::ReadOnly)) {
467 std::cerr << qPrintable(tr("File %1: Cannot open 'CopyrightFile' %2.\n")
468 .arg(QDir::toNativeSeparators(filePath),
469 QDir::toNativeSeparators(p.copyrightFile)));
470 validPackage = false;
471 }
472 p.copyrightFileContents = QString::fromUtf8(file.readAll());
473 }
474
475 if (p.licenseFiles.isEmpty() && !autoDetectLicenseFiles(p))
476 return std::nullopt;
477
478 for (const QString &licenseFile : std::as_const(p.licenseFiles)) {
479 QFile file(licenseFile);
480 if (!file.open(QIODevice::ReadOnly)) {
481 if (logLevel != SilentLog) {
482 std::cerr << qPrintable(tr("File %1: Cannot open 'LicenseFile' %2.\n")
483 .arg(QDir::toNativeSeparators(filePath),
484 QDir::toNativeSeparators(licenseFile)));
485 }
486 validPackage = false;
487 }
488 p.licenseFilesContents << QString::fromUtf8(file.readAll()).trimmed();
489 }
490
491 if (!validPackage)
492 return std::nullopt;
493
494 return p;
495}
496
497// Parses a package's details from a README.chromium file
498static Package parseChromiumFile(QFile &file, const QString &filePath, LogLevel logLevel)
499{
500 const QString directory = QFileInfo(filePath).absolutePath();
501
502 // Parse the fields in the file
503 QHash<QString, QString> fields;
504
505 QTextStream in(&file);
506 while (!in.atEnd()) {
507 QString line = in.readLine().trimmed();
508 QStringList parts = line.split(u":"_s);
509
510 if (parts.size() < 2)
511 continue;
512
513 QString key = parts.at(0);
514 parts.removeFirst();
515 QString value = parts.join(QString()).trimmed();
516
517 fields[key] = value;
518
519 if (line == "Description:"_L1) { // special field : should handle multi-lines values
520 while (!in.atEnd()) {
521 QString line = in.readLine().trimmed();
522
523 if (line.startsWith("Local Modifications:"_L1)) // Don't include this part
524 break;
525
526 fields[key] += line + u"\n"_s;
527 }
528
529 break;
530 }
531 }
532
533 // Construct the Package object
534 Package p;
535
536 QString shortName = fields.contains("Short Name"_L1)
537 ? fields["Short Name"_L1]
538 : fields["Name"_L1];
539 QString version = fields[u"Version"_s];
540
541 p.filePath = QFileInfo(filePath).absoluteFilePath();
542 p.id = u"chromium-"_s + shortName.toLower().replace(QChar::Space, u"-"_s);
543 p.name = fields[u"Name"_s];
544 if (version != QLatin1Char('0')) // "0" : not applicable
545 p.version = version;
546 p.license = fields[u"License"_s];
547 p.homepage = fields[u"URL"_s];
548 p.qdocModule = u"qtwebengine"_s;
549 p.qtUsage = u"Used in Qt WebEngine"_s;
550 p.description = fields[u"Description"_s].trimmed();
551 p.path = directory;
552
553 QString licenseFile = fields[u"License File"_s];
554 if (licenseFile != QString() && licenseFile != "NOT_SHIPPED"_L1) {
555 p.licenseFiles = QStringList(QDir(directory).absoluteFilePath(licenseFile));
556 } else {
557 // Look for a LICENSE or COPYING file as a fallback
558 QDir dir = directory;
559
560 dir.setNameFilters({ u"LICENSE"_s, u"COPYING"_s });
561 dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
562
563 const QFileInfoList entries = dir.entryInfoList();
564 if (!entries.empty())
565 p.licenseFiles = QStringList(entries.at(0).absoluteFilePath());
566 }
567
568 // let's ignore warnings regarding Chromium files for now
569 Q_UNUSED(validatePackage(p, {}, logLevel));
570
571 return p;
572}
573
575{
576 int line = -1;
577 int column = -1;
578};
579
580static CursorPosition mapFromOffset(const QByteArray &content, int offset)
581{
582 CursorPosition pos{ 1, 1 };
583 for (int i = 0; i < content.size(); ++i) {
584 if (i == offset)
585 return pos;
586
587 if (content[i] == '\n') {
588 pos.line++;
589 pos.column = 1;
590 } else {
591 pos.column++;
592 }
593 }
594 return CursorPosition();
595}
596
597std::optional<QList<Package>> readFile(const QString &filePath, LogLevel logLevel)
598{
599 QList<Package> packages;
600 bool errorsFound = false;
601
602 if (logLevel == VerboseLog) {
603 std::cerr << qPrintable(tr("Reading file %1...").arg(
604 QDir::toNativeSeparators(filePath))) << std::endl;
605 }
606 QFile file(filePath);
607 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
608 if (logLevel != SilentLog)
609 std::cerr << qPrintable(tr("Could not open file %1.").arg(
610 QDir::toNativeSeparators(file.fileName()))) << std::endl;
611 return std::nullopt;
612 }
613
614 if (filePath.endsWith(".json"_L1)) {
615 QJsonParseError jsonParseError;
616 const QByteArray content = file.readAll();
617 const QJsonDocument document = QJsonDocument::fromJson(content, &jsonParseError);
618 if (document.isNull()) {
619 if (logLevel != SilentLog) {
620 const CursorPosition pos = mapFromOffset(content, jsonParseError.offset);
621 std::cerr << qPrintable(tr("Could not parse file %1: %2 at line %3, column %4")
622 .arg(QDir::toNativeSeparators(file.fileName()),
623 jsonParseError.errorString(),
624 QString::number(pos.line),
625 QString::number(pos.column)))
626 << std::endl;
627 }
628 return std::nullopt;
629 }
630
631 if (document.isObject()) {
632 std::optional<Package> p =
633 readPackage(document.object(), file.fileName(), logLevel);
634 if (p) {
635 packages << *p;
636 } else {
637 errorsFound = true;
638 }
639 } else if (document.isArray()) {
640 QJsonArray array = document.array();
641 for (int i = 0, size = array.size(); i < size; ++i) {
642 QJsonValue value = array.at(i);
643 if (value.isObject()) {
644 std::optional<Package> p =
645 readPackage(value.toObject(), file.fileName(), logLevel);
646 if (p) {
647 packages << *p;
648 } else {
649 errorsFound = true;
650 }
651 } else {
652 if (logLevel != SilentLog) {
653 std::cerr << qPrintable(tr("File %1: Expecting JSON object in array.")
654 .arg(QDir::toNativeSeparators(file.fileName())))
655 << std::endl;
656 }
657 errorsFound = true;
658 }
659 }
660 } else {
661 if (logLevel != SilentLog) {
662 std::cerr << qPrintable(tr("File %1: Expecting JSON object in array.").arg(
663 QDir::toNativeSeparators(file.fileName()))) << std::endl;
664 }
665 errorsFound = true;
666 }
667 } else if (filePath.endsWith(".chromium"_L1)) {
668 Package chromiumPackage = parseChromiumFile(file, filePath, logLevel);
669 if (!chromiumPackage.name.isEmpty()) // Skip invalid README.chromium files
670 packages << chromiumPackage;
671 } else {
672 if (logLevel != SilentLog) {
673 std::cerr << qPrintable(tr("File %1: Unsupported file type.")
674 .arg(QDir::toNativeSeparators(file.fileName())))
675 << std::endl;
676 }
677 errorsFound = true;
678 }
679
680 if (errorsFound)
681 return std::nullopt;
682 return packages;
683}
684
685std::optional<QList<Package>> scanDirectory(const QString &directory, InputFormats inputFormats,
686 LogLevel logLevel)
687{
688 QDir dir(directory);
689 QList<Package> packages;
690 bool errorsFound = false;
691
692 QStringList nameFilters = QStringList();
693 if (inputFormats & InputFormat::QtAttributions)
694 nameFilters << u"qt_attribution.json"_s;
695 if (inputFormats & InputFormat::ChromiumAttributions)
696 nameFilters << u"README.chromium"_s;
697 if (qEnvironmentVariableIsSet("QT_ATTRIBUTIONSSCANNER_TEST"))
698 nameFilters << u"qt_attribution_test.json"_s << u"README_test.chromium"_s;
699
700 dir.setNameFilters(nameFilters);
701 dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Files);
702
703 const QFileInfoList entries = dir.entryInfoList();
704 for (const QFileInfo &info : entries) {
705 if (info.isDir()) {
706 std::optional<QList<Package>> ps =
707 scanDirectory(info.filePath(), inputFormats, logLevel);
708 if (!ps)
709 errorsFound = true;
710 else
711 packages += *ps;
712 } else {
713 std::optional p = readFile(info.filePath(), logLevel);
714 if (!p)
715 errorsFound = true;
716 else
717 packages += *p;
718 }
719 }
720
721 if (errorsFound)
722 return std::nullopt;
723 return packages;
724}
725
726} // namespace Scanner
LogLevel
Definition logging.h:9
@ SilentLog
Definition logging.h:12
@ VerboseLog
Definition logging.h:10
static QStringList extractLicenseIdsFromSPDXExpression(QString expression)
Definition scanner.cpp:165
std::optional< QList< Package > > scanDirectory(const QString &directory, InputFormats inputFormats, LogLevel logLevel)
Definition scanner.cpp:685
static CursorPosition mapFromOffset(const QByteArray &content, int offset)
Definition scanner.cpp:580
static void missingPropertyWarning(const QString &filePath, const QString &property)
Definition scanner.cpp:21
static QString locateLicensesDir(const QString &packageDir)
Definition scanner.cpp:194
static bool handleStringOrStringArrayJsonKey(QStringList &outList, const QString &key, QJsonValueConstRef jsonValue, const QString &filePath, LogLevel logLevel)
Definition scanner.cpp:246
static Package parseChromiumFile(QFile &file, const QString &filePath, LogLevel logLevel)
Definition scanner.cpp:498
static std::optional< QStringList > toStringList(const QJsonValue &value)
Definition scanner.cpp:136
bool validatePackage(Package &p, Checks checks, LogLevel logLevel)
Definition scanner.cpp:27
static std::optional< Package > readPackage(const QJsonObject &object, const QString &filePath, LogLevel logLevel)
Definition scanner.cpp:269
static bool autoDetectLicenseFiles(Package &p)
Definition scanner.cpp:211
static std::optional< QString > arrayToMultiLineString(const QJsonValue &value)
Definition scanner.cpp:149
bool securityCritical
Definition package.h:24