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))
46 missingPropertyWarning(filePath, u"Name"_s);
52 missingPropertyWarning(filePath, u"Id"_s);
54 }
else if (!p.id.isLower() || p.id.contains(
' '_L1)) {
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()) {
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)))
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) {
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());
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;
409 const QString versionVar = u"$<VERSION>"_s;
410 const QString versionDashedVar = u"$<VERSION_DASHED>"_s;
411 auto replaceInString = [&](QString &s) {
412 if (s.contains(versionVar) || s.contains(versionDashedVar)) {
413 if (p.version.isEmpty()) {
415 std::cerr << qPrintable(
416 tr(
"File %1: $<VERSION> used but 'Version' is not set.")
417 .arg(QDir::toNativeSeparators(filePath)))
420 validPackage =
false;
423 s.replace(versionVar, p.version);
424 s.replace(versionDashedVar,
425 QString(p.version).replace(u'.', u'-'));
428 auto replaceInList = [&](QStringList &list) {
429 for (QString &s : list)
432 replaceInString(p.name);
433 replaceInString(p.homepage);
434 replaceInString(p.downloadLocation);
435 replaceInString(p.description);
436 replaceInString(p.qtUsage);
437 replaceInString(p.packageComment);
438 replaceInList(p.cpeList);
439 replaceInList(p.purlList);
442 if (!p.copyrightFile.isEmpty()) {
443 QFile file(p.copyrightFile);
444 if (!file.open(QIODevice::ReadOnly)) {
445 std::cerr << qPrintable(tr(
"File %1: Cannot open 'CopyrightFile' %2.\n")
446 .arg(QDir::toNativeSeparators(filePath),
447 QDir::toNativeSeparators(p.copyrightFile)));
448 validPackage =
false;
450 p.copyrightFileContents = QString::fromUtf8(file.readAll());
456 for (
const QString &licenseFile : std::as_const(p.licenseFiles)) {
457 QFile file(licenseFile);
458 if (!file.open(QIODevice::ReadOnly)) {
459 if (logLevel != SilentLog) {
460 std::cerr << qPrintable(tr(
"File %1: Cannot open 'LicenseFile' %2.\n")
461 .arg(QDir::toNativeSeparators(filePath),
462 QDir::toNativeSeparators(licenseFile)));
464 validPackage =
false;
466 p.licenseFilesContents << QString::fromUtf8(file.readAll()).trimmed();
478 const QString directory = QFileInfo(filePath).absolutePath();
481 QHash<QString, QString> fields;
483 QTextStream in(&file);
484 while (!in.atEnd()) {
485 QString line = in.readLine().trimmed();
486 QStringList parts = line.split(u":"_s);
488 if (parts.size() < 2)
491 QString key = parts.at(0);
493 QString value = parts.join(QString()).trimmed();
497 if (line ==
"Description:"_L1) {
498 while (!in.atEnd()) {
499 QString line = in.readLine().trimmed();
501 if (line.startsWith(
"Local Modifications:"_L1))
504 fields[key] += line + u"\n"_s;
514 QString shortName = fields.contains(
"Short Name"_L1)
515 ? fields[
"Short Name"_L1]
517 QString version = fields[u"Version"_s];
519 p.filePath = QFileInfo(filePath).absoluteFilePath();
520 p.id = u"chromium-"_s + shortName.toLower().replace(QChar::Space, u"-"_s);
521 p.name = fields[u"Name"_s];
522 if (version != QLatin1Char(
'0'))
524 p.license = fields[u"License"_s];
525 p.homepage = fields[u"URL"_s];
526 p.qdocModule = u"qtwebengine"_s;
527 p.qtUsage = u"Used in Qt WebEngine"_s;
528 p.description = fields[u"Description"_s].trimmed();
531 QString licenseFile = fields[u"License File"_s];
532 if (licenseFile != QString() && licenseFile !=
"NOT_SHIPPED"_L1) {
533 p.licenseFiles = QStringList(QDir(directory).absoluteFilePath(licenseFile));
536 QDir dir = directory;
538 dir.setNameFilters({ u"LICENSE"_s, u"COPYING"_s });
539 dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
541 const QFileInfoList entries = dir.entryInfoList();
542 if (!entries.empty())
543 p.licenseFiles = QStringList(entries.at(0).absoluteFilePath());
547 Q_UNUSED(validatePackage(p, {}, logLevel));
575std::optional<QList<Package>> readFile(
const QString &filePath,
LogLevel logLevel)
577 QList<Package> packages;
578 bool errorsFound =
false;
581 std::cerr << qPrintable(tr(
"Reading file %1...").arg(
582 QDir::toNativeSeparators(filePath))) <<
std::endl;
584 QFile file(filePath);
585 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
587 std::cerr << qPrintable(tr(
"Could not open file %1.").arg(
588 QDir::toNativeSeparators(file.fileName()))) <<
std::endl;
592 if (filePath.endsWith(
".json"_L1)) {
593 QJsonParseError jsonParseError;
594 const QByteArray content = file.readAll();
595 const QJsonDocument document = QJsonDocument::fromJson(content, &jsonParseError);
596 if (document.isNull()) {
598 const CursorPosition pos = mapFromOffset(content, jsonParseError.offset);
599 std::cerr << qPrintable(tr(
"Could not parse file %1: %2 at line %3, column %4")
600 .arg(QDir::toNativeSeparators(file.fileName()),
601 jsonParseError.errorString(),
602 QString::number(pos.line),
603 QString::number(pos.column)))
609 if (document.isObject()) {
611 readPackage(document.object(), file.fileName(), logLevel);
617 }
else if (document.isArray()) {
618 QJsonArray array = document.array();
619 for (
int i = 0, size = array.size(); i < size; ++i) {
620 QJsonValue value = array.at(i);
621 if (value.isObject()) {
623 readPackage(value.toObject(), file.fileName(), logLevel);
631 std::cerr << qPrintable(tr(
"File %1: Expecting JSON object in array.")
632 .arg(QDir::toNativeSeparators(file.fileName())))
640 std::cerr << qPrintable(tr(
"File %1: Expecting JSON object in array.").arg(
641 QDir::toNativeSeparators(file.fileName()))) <<
std::endl;
645 }
else if (filePath.endsWith(
".chromium"_L1)) {
646 Package chromiumPackage = parseChromiumFile(file, filePath, logLevel);
647 if (!chromiumPackage.name.isEmpty())
648 packages << chromiumPackage;
651 std::cerr << qPrintable(tr(
"File %1: Unsupported file type.")
652 .arg(QDir::toNativeSeparators(file.fileName())))
667 QList<Package> packages;
668 bool errorsFound =
false;
670 QStringList nameFilters = QStringList();
671 if (inputFormats & InputFormat::QtAttributions)
672 nameFilters << u"qt_attribution.json"_s;
673 if (inputFormats & InputFormat::ChromiumAttributions)
674 nameFilters << u"README.chromium"_s;
675 if (qEnvironmentVariableIsSet(
"QT_ATTRIBUTIONSSCANNER_TEST"))
676 nameFilters << u"qt_attribution_test.json"_s << u"README_test.chromium"_s;
678 dir.setNameFilters(nameFilters);
679 dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Files);
681 const QFileInfoList entries = dir.entryInfoList();
682 for (
const QFileInfo &info : entries) {
684 std::optional<QList<Package>> ps =
685 scanDirectory(info.filePath(), inputFormats, logLevel);
691 std::optional p = readFile(info.filePath(), logLevel);