29 const auto &filePath = p.filePath;
30 bool validPackage =
true;
32 if (filePath.isEmpty()) {
33 std::cerr << qPrintable(tr(
"The origin file of package '%1' was not recorded.")
38 if (p.qtParts.isEmpty())
39 p.qtParts << u"libs"_s;
41 if (p.name.isEmpty()) {
42 if (p.id.startsWith(
"chromium-"_L1))
45 if (logLevel != SilentLog)
46 missingPropertyWarning(filePath, u"Name"_s);
51 if (logLevel != SilentLog)
52 missingPropertyWarning(filePath, u"Id"_s);
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;
61 if (p.license.isEmpty()) {
62 if (logLevel != SilentLog)
63 missingPropertyWarning(filePath, u"License"_s);
67 if (!p.copyright.isEmpty() && !p.copyrightFile.isEmpty()) {
69 std::cerr << qPrintable(tr(
"File %1: Properties 'Copyright' and 'CopyrightFile' are "
70 "mutually exclusive.")
71 .arg(QDir::toNativeSeparators(filePath)))
78 if (logLevel != SilentLog)
79 missingPropertyWarning(filePath, u"DownloadLocation"_s);
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) {
87 if (logLevel != SilentLog) {
88 std::cerr << qPrintable(tr(
"File %1: Property 'QtPart' contains unknown element "
89 "'%2'. Valid entries are 'examples', 'tests', 'tools' "
91 QDir::toNativeSeparators(filePath), part))
98 if (!(checks & Check::Paths))
101 const QDir dir = p.path;
103 std::cerr << qPrintable(
104 tr(
"File %1: Directory '%2' does not exist.")
105 .arg(QDir::toNativeSeparators(filePath), QDir::toNativeSeparators(p.path)))
107 validPackage =
false;
109 for (
const QString &file : std::as_const(p.files)) {
110 if (!dir.exists(file)) {
111 if (logLevel != SilentLog) {
112 std::cerr << qPrintable(
113 tr(
"File %1: Path '%2' does not exist in directory '%3'.")
114 .arg(QDir::toNativeSeparators(filePath),
115 QDir::toNativeSeparators(file),
116 QDir::toNativeSeparators(p.path)))
119 validPackage =
false;
204 const QString licensesDirPath = locateLicensesDir(p.path);
205 const QStringList licenseIds = extractLicenseIdsFromSPDXExpression(p.licenseId);
208 QDir licensesDir(licensesDirPath);
209 QDir licensesDirLocal = p.path;
210 for (
const QString &id : licenseIds) {
211 QString fileName = id + u".txt";
212 QString fileNameLocal = u"LICENSE." + id + u".txt";
214 if (licensesDirLocal.exists(fileNameLocal)) {
215 p.licenseFiles.append(licensesDirLocal.filePath(fileNameLocal));
216 }
else if (licensesDir.exists(fileName)) {
217 p.licenseFiles.append(licensesDir.filePath(fileName));
219 std::cerr << qPrintable(tr(
"Missing expected license file:")) << std::endl;
220 std::cerr << qPrintable(QDir::toNativeSeparators(licensesDirLocal.filePath(fileNameLocal)))
222 if (!licensesDirPath.isEmpty()) {
223 std::cerr << qPrintable(tr(
"or\n %1").arg(
224 QDir::toNativeSeparators(licensesDir.filePath(fileName))))
264 bool validPackage =
true;
265 const QString directory = QFileInfo(filePath).absolutePath();
266 p.filePath = QFileInfo(filePath).absoluteFilePath();
269 for (
auto iter = object.constBegin(); iter != object.constEnd(); ++iter) {
270 const QString key = iter.key();
272 if (!iter.value().isString() && key !=
"QtParts"_L1 && key !=
"SecurityCritical"_L1
273 && key !=
"Files"_L1 && key !=
"LicenseFiles"_L1 && key !=
"Comment"_L1
274 && key !=
"Copyright"_L1 && key !=
"CPE"_L1 && key !=
"PURL"_L1) {
275 if (logLevel != SilentLog)
276 std::cerr << qPrintable(tr(
"File %1: Expected JSON string as value of %2.").arg(
277 QDir::toNativeSeparators(filePath), key)) << std::endl;
278 validPackage =
false;
281 const QString value = iter.value().toString();
282 if (key ==
"Name"_L1) {
284 }
else if (key ==
"Path"_L1) {
285 p.path = QDir(directory).absoluteFilePath(value);
286 }
else if (key ==
"Files"_L1) {
287 QJsonValueConstRef jsonValue = iter.value();
288 if (jsonValue.isArray()) {
289 auto maybeStringList = toStringList(jsonValue);
291 p.files = maybeStringList.value();
292 }
else if (jsonValue.isString()) {
294 p.files = value.simplified().split(QLatin1Char(
' '), Qt::SkipEmptyParts);
297 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings as value "
298 "of Files.").arg(QDir::toNativeSeparators(filePath)))
300 validPackage =
false;
304 }
else if (key ==
"Comment"_L1) {
309 }
else if (key ==
"Id"_L1) {
311 }
else if (key ==
"Homepage"_L1) {
313 }
else if (key ==
"Version"_L1) {
315 }
else if (key ==
"DownloadLocation"_L1) {
316 p.downloadLocation = value;
317 }
else if (key ==
"License"_L1) {
319 }
else if (key ==
"LicenseId"_L1) {
321 }
else if (key ==
"LicenseFile"_L1) {
322 p.licenseFiles = QStringList(QDir(directory).absoluteFilePath(value));
323 }
else if (key ==
"LicenseFiles"_L1) {
324 auto strings = toStringList(iter.value());
326 if (logLevel != SilentLog)
327 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings in %2.")
328 .arg(QDir::toNativeSeparators(filePath), key))
330 validPackage =
false;
333 const QDir dir(directory);
334 for (
const auto &iter : std::as_const(strings.value()))
335 p.licenseFiles.push_back(dir.absoluteFilePath(iter));
336 }
else if (key ==
"Copyright"_L1) {
337 QJsonValueConstRef jsonValue = iter.value();
338 if (jsonValue.isArray()) {
340 auto maybeString = arrayToMultiLineString(jsonValue);
342 p.copyright = maybeString.value();
343 }
else if (jsonValue.isString()) {
348 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings or "
349 "string as value of %2.").arg(
350 QDir::toNativeSeparators(filePath), key)) << std::endl;
351 validPackage =
false;
355 }
else if (key ==
"CPE"_L1) {
356 const QJsonValueConstRef jsonValue = iter.value();
357 if (!handleStringOrStringArrayJsonKey(p.cpeList, key, jsonValue, filePath, logLevel)) {
358 validPackage =
false;
361 }
else if (key ==
"PURL"_L1) {
362 const QJsonValueConstRef jsonValue = iter.value();
363 if (!handleStringOrStringArrayJsonKey(p.purlList, key, jsonValue, filePath, logLevel)) {
364 validPackage =
false;
367 }
else if (key ==
"CopyrightFile"_L1) {
368 p.copyrightFile = QDir(directory).absoluteFilePath(value);
369 }
else if (key ==
"PackageComment"_L1) {
370 p.packageComment = value;
371 }
else if (key ==
"QDocModule"_L1) {
372 p.qdocModule = value;
373 }
else if (key ==
"Description"_L1) {
374 p.description = value;
375 }
else if (key ==
"QtUsage"_L1) {
377 }
else if (key ==
"SecurityCritical"_L1) {
378 if (!iter.value().isBool()) {
379 std::cerr << qPrintable(tr(
"File %1: Expected JSON boolean in %2.")
380 .arg(QDir::toNativeSeparators(filePath), key))
382 validPackage =
false;
386 }
else if (key ==
"QtParts"_L1) {
387 auto parts = toStringList(iter.value());
390 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings in %2.")
391 .arg(QDir::toNativeSeparators(filePath), key))
394 validPackage =
false;
397 p.qtParts = parts.value();
400 std::cerr << qPrintable(tr(
"File %1: Unknown key %2.").arg(
401 QDir::toNativeSeparators(filePath), key)) << std::endl;
403 validPackage =
false;
407 if (!p.copyrightFile.isEmpty()) {
408 QFile file(p.copyrightFile);
409 if (!file.open(QIODevice::ReadOnly)) {
410 std::cerr << qPrintable(tr(
"File %1: Cannot open 'CopyrightFile' %2.\n")
411 .arg(QDir::toNativeSeparators(filePath),
412 QDir::toNativeSeparators(p.copyrightFile)));
413 validPackage =
false;
415 p.copyrightFileContents = QString::fromUtf8(file.readAll());
421 for (
const QString &licenseFile : std::as_const(p.licenseFiles)) {
422 QFile file(licenseFile);
423 if (!file.open(QIODevice::ReadOnly)) {
424 if (logLevel != SilentLog) {
425 std::cerr << qPrintable(tr(
"File %1: Cannot open 'LicenseFile' %2.\n")
426 .arg(QDir::toNativeSeparators(filePath),
427 QDir::toNativeSeparators(licenseFile)));
429 validPackage =
false;
431 p.licenseFilesContents << QString::fromUtf8(file.readAll()).trimmed();
443 const QString directory = QFileInfo(filePath).absolutePath();
446 QHash<QString, QString> fields;
448 QTextStream in(&file);
449 while (!in.atEnd()) {
450 QString line = in.readLine().trimmed();
451 QStringList parts = line.split(u":"_s);
453 if (parts.size() < 2)
456 QString key = parts.at(0);
458 QString value = parts.join(QString()).trimmed();
462 if (line ==
"Description:"_L1) {
463 while (!in.atEnd()) {
464 QString line = in.readLine().trimmed();
466 if (line.startsWith(
"Local Modifications:"_L1))
469 fields[key] += line + u"\n"_s;
479 QString shortName = fields.contains(
"Short Name"_L1)
480 ? fields[
"Short Name"_L1]
482 QString version = fields[u"Version"_s];
484 p.filePath = QFileInfo(filePath).absoluteFilePath();
485 p.id = u"chromium-"_s + shortName.toLower().replace(QChar::Space, u"-"_s);
486 p.name = fields[u"Name"_s];
487 if (version != QLatin1Char(
'0'))
489 p.license = fields[u"License"_s];
490 p.homepage = fields[u"URL"_s];
491 p.qdocModule = u"qtwebengine"_s;
492 p.qtUsage = u"Used in Qt WebEngine"_s;
493 p.description = fields[u"Description"_s].trimmed();
496 QString licenseFile = fields[u"License File"_s];
497 if (licenseFile != QString() && licenseFile !=
"NOT_SHIPPED"_L1) {
498 p.licenseFiles = QStringList(QDir(directory).absoluteFilePath(licenseFile));
501 QDir dir = directory;
503 dir.setNameFilters({ u"LICENSE"_s, u"COPYING"_s });
504 dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
506 const QFileInfoList entries = dir.entryInfoList();
507 if (!entries.empty())
508 p.licenseFiles = QStringList(entries.at(0).absoluteFilePath());
512 Q_UNUSED(validatePackage(p, {}, logLevel));
540std::optional<QList<Package>> readFile(
const QString &filePath,
LogLevel logLevel)
542 QList<Package> packages;
543 bool errorsFound =
false;
546 std::cerr << qPrintable(tr(
"Reading file %1...").arg(
547 QDir::toNativeSeparators(filePath))) << std::endl;
549 QFile file(filePath);
550 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
551 if (logLevel != SilentLog)
552 std::cerr << qPrintable(tr(
"Could not open file %1.").arg(
553 QDir::toNativeSeparators(file.fileName()))) << std::endl;
557 if (filePath.endsWith(
".json"_L1)) {
558 QJsonParseError jsonParseError;
559 const QByteArray content = file.readAll();
560 const QJsonDocument document = QJsonDocument::fromJson(content, &jsonParseError);
561 if (document.isNull()) {
563 const CursorPosition pos = mapFromOffset(content, jsonParseError.offset);
564 std::cerr << qPrintable(tr(
"Could not parse file %1: %2 at line %3, column %4")
565 .arg(QDir::toNativeSeparators(file.fileName()),
566 jsonParseError.errorString(),
567 QString::number(pos.line),
568 QString::number(pos.column)))
574 if (document.isObject()) {
576 readPackage(document.object(), file.fileName(), logLevel);
582 }
else if (document.isArray()) {
583 QJsonArray array = document.array();
584 for (
int i = 0, size = array.size(); i < size; ++i) {
585 QJsonValue value = array.at(i);
586 if (value.isObject()) {
588 readPackage(value.toObject(), file.fileName(), logLevel);
596 std::cerr << qPrintable(tr(
"File %1: Expecting JSON object in array.")
597 .arg(QDir::toNativeSeparators(file.fileName())))
605 std::cerr << qPrintable(tr(
"File %1: Expecting JSON object in array.").arg(
606 QDir::toNativeSeparators(file.fileName()))) << std::endl;
610 }
else if (filePath.endsWith(
".chromium"_L1)) {
611 Package chromiumPackage = parseChromiumFile(file, filePath, logLevel);
612 if (!chromiumPackage.name.isEmpty())
613 packages << chromiumPackage;
616 std::cerr << qPrintable(tr(
"File %1: Unsupported file type.")
617 .arg(QDir::toNativeSeparators(file.fileName())))
632 QList<Package> packages;
633 bool errorsFound =
false;
635 QStringList nameFilters = QStringList();
636 if (inputFormats & InputFormat::QtAttributions)
637 nameFilters << u"qt_attribution.json"_s;
638 if (inputFormats & InputFormat::ChromiumAttributions)
639 nameFilters << u"README.chromium"_s;
640 if (qEnvironmentVariableIsSet(
"QT_ATTRIBUTIONSSCANNER_TEST"))
641 nameFilters << u"qt_attribution_test.json"_s << u"README_test.chromium"_s;
643 dir.setNameFilters(nameFilters);
644 dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Files);
646 const QFileInfoList entries = dir.entryInfoList();
647 for (
const QFileInfo &info : entries) {
649 std::optional<QList<Package>> ps =
650 scanDirectory(info.filePath(), inputFormats, logLevel);
656 std::optional p = readFile(info.filePath(), logLevel);