29 bool validPackage =
true;
31 if (p.qtParts.isEmpty())
32 p.qtParts << u"libs"_s;
34 if (p.name.isEmpty()) {
35 if (p.id.startsWith(
"chromium-"_L1))
38 if (logLevel != SilentLog)
39 missingPropertyWarning(filePath, u"Name"_s);
44 if (logLevel != SilentLog)
45 missingPropertyWarning(filePath, u"Id"_s);
48 if (p.license.isEmpty()) {
49 if (logLevel != SilentLog)
50 missingPropertyWarning(filePath, u"License"_s);
54 if (!p.copyright.isEmpty() && !p.copyrightFile.isEmpty()) {
56 std::cerr << qPrintable(tr(
"File %1: Properties 'Copyright' and 'CopyrightFile' are "
57 "mutually exclusive.")
58 .arg(QDir::toNativeSeparators(filePath)))
65 if (logLevel != SilentLog)
66 missingPropertyWarning(filePath, u"DownloadLocation"_s);
70 for (
const QString &part : std::as_const(p.qtParts)) {
71 if (part !=
"examples"_L1 && part !=
"tests"_L1
72 && part !=
"tools"_L1 && part !=
"libs"_L1) {
74 if (logLevel != SilentLog) {
75 std::cerr << qPrintable(tr(
"File %1: Property 'QtPart' contains unknown element "
76 "'%2'. Valid entries are 'examples', 'tests', 'tools' "
78 QDir::toNativeSeparators(filePath), part))
85 if (!(checks & Check::Paths))
88 const QDir dir = p.path;
90 std::cerr << qPrintable(
91 tr(
"File %1: Directory '%2' does not exist.")
92 .arg(QDir::toNativeSeparators(filePath), QDir::toNativeSeparators(p.path)))
96 for (
const QString &file : std::as_const(p.files)) {
97 if (!dir.exists(file)) {
98 if (logLevel != SilentLog) {
99 std::cerr << qPrintable(
100 tr(
"File %1: Path '%2' does not exist in directory '%3'.")
101 .arg(QDir::toNativeSeparators(filePath),
102 QDir::toNativeSeparators(file),
103 QDir::toNativeSeparators(p.path)))
106 validPackage =
false;
191 const QString licensesDirPath = locateLicensesDir(p.path);
192 const QStringList licenseIds = extractLicenseIdsFromSPDXExpression(p.licenseId);
193 if (!licenseIds.isEmpty() && licensesDirPath.isEmpty()) {
194 std::cerr << qPrintable(tr(
"LICENSES directory could not be located.")) << std::endl;
199 QDir licensesDir(licensesDirPath);
200 for (
const QString &id : licenseIds) {
201 QString fileName = id + u".txt";
202 if (licensesDir.exists(fileName)) {
203 p.licenseFiles.append(licensesDir.filePath(fileName));
205 std::cerr << qPrintable(tr(
"Expected license file not found: %1").arg(
206 QDir::toNativeSeparators(licensesDir.filePath(fileName))))
245 bool validPackage =
true;
246 const QString directory = QFileInfo(filePath).absolutePath();
249 for (
auto iter = object.constBegin(); iter != object.constEnd(); ++iter) {
250 const QString key = iter.key();
252 if (!iter.value().isString() && key !=
"QtParts"_L1 && key !=
"SecurityCritical"_L1
253 && key !=
"Files"_L1 && key !=
"LicenseFiles"_L1 && key !=
"Comment"_L1
254 && key !=
"Copyright"_L1 && key !=
"CPE"_L1 && key !=
"PURL"_L1) {
255 if (logLevel != SilentLog)
256 std::cerr << qPrintable(tr(
"File %1: Expected JSON string as value of %2.").arg(
257 QDir::toNativeSeparators(filePath), key)) << std::endl;
258 validPackage =
false;
261 const QString value = iter.value().toString();
262 if (key ==
"Name"_L1) {
264 }
else if (key ==
"Path"_L1) {
265 p.path = QDir(directory).absoluteFilePath(value);
266 }
else if (key ==
"Files"_L1) {
267 QJsonValueConstRef jsonValue = iter.value();
268 if (jsonValue.isArray()) {
269 auto maybeStringList = toStringList(jsonValue);
271 p.files = maybeStringList.value();
272 }
else if (jsonValue.isString()) {
274 p.files = value.simplified().split(QLatin1Char(
' '), Qt::SkipEmptyParts);
277 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings as value "
279 validPackage =
false;
283 }
else if (key ==
"Comment"_L1) {
288 }
else if (key ==
"Id"_L1) {
290 }
else if (key ==
"Homepage"_L1) {
292 }
else if (key ==
"Version"_L1) {
294 }
else if (key ==
"DownloadLocation"_L1) {
295 p.downloadLocation = value;
296 }
else if (key ==
"License"_L1) {
298 }
else if (key ==
"LicenseId"_L1) {
300 }
else if (key ==
"LicenseFile"_L1) {
301 p.licenseFiles = QStringList(QDir(directory).absoluteFilePath(value));
302 }
else if (key ==
"LicenseFiles"_L1) {
303 auto strings = toStringList(iter.value());
305 if (logLevel != SilentLog)
306 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings in %2.")
307 .arg(QDir::toNativeSeparators(filePath), key))
309 validPackage =
false;
312 const QDir dir(directory);
313 for (
const auto &iter : std::as_const(strings.value()))
314 p.licenseFiles.push_back(dir.absoluteFilePath(iter));
315 }
else if (key ==
"Copyright"_L1) {
316 QJsonValueConstRef jsonValue = iter.value();
317 if (jsonValue.isArray()) {
319 auto maybeString = arrayToMultiLineString(jsonValue);
321 p.copyright = maybeString.value();
322 }
else if (jsonValue.isString()) {
327 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings or "
328 "string as value of %2.").arg(
329 QDir::toNativeSeparators(filePath), key)) << std::endl;
330 validPackage =
false;
334 }
else if (key ==
"CPE"_L1) {
335 const QJsonValueConstRef jsonValue = iter.value();
336 if (!handleStringOrStringArrayJsonKey(p.cpeList, key, jsonValue, filePath, logLevel)) {
337 validPackage =
false;
340 }
else if (key ==
"PURL"_L1) {
341 const QJsonValueConstRef jsonValue = iter.value();
342 if (!handleStringOrStringArrayJsonKey(p.purlList, key, jsonValue, filePath, logLevel)) {
343 validPackage =
false;
346 }
else if (key ==
"CopyrightFile"_L1) {
347 p.copyrightFile = QDir(directory).absoluteFilePath(value);
348 }
else if (key ==
"PackageComment"_L1) {
349 p.packageComment = value;
350 }
else if (key ==
"QDocModule"_L1) {
351 p.qdocModule = value;
352 }
else if (key ==
"Description"_L1) {
353 p.description = value;
354 }
else if (key ==
"QtUsage"_L1) {
356 }
else if (key ==
"SecurityCritical"_L1) {
357 if (!iter.value().isBool()) {
358 std::cerr << qPrintable(tr(
"File %1: Expected JSON boolean in %2.")
359 .arg(QDir::toNativeSeparators(filePath), key))
361 validPackage =
false;
365 }
else if (key ==
"QtParts"_L1) {
366 auto parts = toStringList(iter.value());
369 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings in %2.")
370 .arg(QDir::toNativeSeparators(filePath), key))
373 validPackage =
false;
376 p.qtParts = parts.value();
379 std::cerr << qPrintable(tr(
"File %1: Unknown key %2.").arg(
380 QDir::toNativeSeparators(filePath), key)) << std::endl;
382 validPackage =
false;
386 if (!p.copyrightFile.isEmpty()) {
387 QFile file(p.copyrightFile);
388 if (!file.open(QIODevice::ReadOnly)) {
389 std::cerr << qPrintable(tr(
"File %1: Cannot open 'CopyrightFile' %2.\n")
390 .arg(QDir::toNativeSeparators(filePath),
391 QDir::toNativeSeparators(p.copyrightFile)));
392 validPackage =
false;
394 p.copyrightFileContents = QString::fromUtf8(file.readAll());
400 for (
const QString &licenseFile : std::as_const(p.licenseFiles)) {
401 QFile file(licenseFile);
402 if (!file.open(QIODevice::ReadOnly)) {
403 if (logLevel != SilentLog) {
404 std::cerr << qPrintable(tr(
"File %1: Cannot open 'LicenseFile' %2.\n")
405 .arg(QDir::toNativeSeparators(filePath),
406 QDir::toNativeSeparators(licenseFile)));
408 validPackage =
false;
410 p.licenseFilesContents << QString::fromUtf8(file.readAll()).trimmed();
413 if (!validatePackage(p, filePath, checks, logLevel) || !validPackage)
422 const QString directory = QFileInfo(filePath).absolutePath();
425 QHash<QString, QString> fields;
427 QTextStream in(&file);
428 while (!in.atEnd()) {
429 QString line = in.readLine().trimmed();
430 QStringList parts = line.split(u":"_s);
432 if (parts.size() < 2)
435 QString key = parts.at(0);
437 QString value = parts.join(QString()).trimmed();
441 if (line ==
"Description:"_L1) {
442 while (!in.atEnd()) {
443 QString line = in.readLine().trimmed();
445 if (line.startsWith(
"Local Modifications:"_L1))
448 fields[key] += line + u"\n"_s;
458 QString shortName = fields.contains(
"Short Name"_L1)
459 ? fields[
"Short Name"_L1]
461 QString version = fields[u"Version"_s];
463 p.id = u"chromium-"_s + shortName.toLower().replace(QChar::Space, u"-"_s);
464 p.name = fields[u"Name"_s];
465 if (version != QLatin1Char(
'0'))
467 p.license = fields[u"License"_s];
468 p.homepage = fields[u"URL"_s];
469 p.qdocModule = u"qtwebengine"_s;
470 p.qtUsage = u"Used in Qt WebEngine"_s;
471 p.description = fields[u"Description"_s].trimmed();
474 QString licenseFile = fields[u"License File"_s];
475 if (licenseFile != QString() && licenseFile !=
"NOT_SHIPPED"_L1) {
476 p.licenseFiles = QStringList(QDir(directory).absoluteFilePath(licenseFile));
479 QDir dir = directory;
481 dir.setNameFilters({ u"LICENSE"_s, u"COPYING"_s });
482 dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
484 const QFileInfoList entries = dir.entryInfoList();
485 if (!entries.empty())
486 p.licenseFiles = QStringList(entries.at(0).absoluteFilePath());
490 Q_UNUSED(validatePackage(p, filePath, {}, logLevel));
518std::optional<QList<Package>> readFile(
const QString &filePath, Checks checks,
LogLevel logLevel)
520 QList<Package> packages;
521 bool errorsFound =
false;
524 std::cerr << qPrintable(tr(
"Reading file %1...").arg(
525 QDir::toNativeSeparators(filePath))) << std::endl;
527 QFile file(filePath);
528 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
529 if (logLevel != SilentLog)
530 std::cerr << qPrintable(tr(
"Could not open file %1.").arg(
531 QDir::toNativeSeparators(file.fileName()))) << std::endl;
535 if (filePath.endsWith(
".json"_L1)) {
536 QJsonParseError jsonParseError;
537 const QByteArray content = file.readAll();
538 const QJsonDocument document = QJsonDocument::fromJson(content, &jsonParseError);
539 if (document.isNull()) {
541 const CursorPosition pos = mapFromOffset(content, jsonParseError.offset);
542 std::cerr << qPrintable(tr(
"Could not parse file %1: %2 at line %3, column %4")
543 .arg(QDir::toNativeSeparators(file.fileName()),
544 jsonParseError.errorString(),
545 QString::number(pos.line),
546 QString::number(pos.column)))
552 if (document.isObject()) {
554 readPackage(document.object(), file.fileName(), checks, logLevel);
560 }
else if (document.isArray()) {
561 QJsonArray array = document.array();
562 for (
int i = 0, size = array.size(); i < size; ++i) {
563 QJsonValue value = array.at(i);
564 if (value.isObject()) {
566 readPackage(value.toObject(), file.fileName(), checks, logLevel);
574 std::cerr << qPrintable(tr(
"File %1: Expecting JSON object in array.")
575 .arg(QDir::toNativeSeparators(file.fileName())))
583 std::cerr << qPrintable(tr(
"File %1: Expecting JSON object in array.").arg(
584 QDir::toNativeSeparators(file.fileName()))) << std::endl;
588 }
else if (filePath.endsWith(
".chromium"_L1)) {
589 Package chromiumPackage = parseChromiumFile(file, filePath, logLevel);
590 if (!chromiumPackage.name.isEmpty())
591 packages << chromiumPackage;
594 std::cerr << qPrintable(tr(
"File %1: Unsupported file type.")
595 .arg(QDir::toNativeSeparators(file.fileName())))
610 QList<Package> packages;
611 bool errorsFound =
false;
613 QStringList nameFilters = QStringList();
614 if (inputFormats & InputFormat::QtAttributions)
615 nameFilters << u"qt_attribution.json"_s;
616 if (inputFormats & InputFormat::ChromiumAttributions)
617 nameFilters << u"README.chromium"_s;
618 if (qEnvironmentVariableIsSet(
"QT_ATTRIBUTIONSSCANNER_TEST"))
619 nameFilters << u"qt_attribution_test.json"_s << u"README_test.chromium"_s;
621 dir.setNameFilters(nameFilters);
622 dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Files);
624 const QFileInfoList entries = dir.entryInfoList();
625 for (
const QFileInfo &info : entries) {
627 std::optional<QList<Package>> ps =
628 scanDirectory(info.filePath(), inputFormats, checks, logLevel);
634 std::optional p = readFile(info.filePath(), checks, logLevel);