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))
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)))
104 validPackage =
false;
107 if (!(checks & Check::Paths))
110 const QDir dir = p.path;
112 std::cerr << qPrintable(
113 tr(
"File %1: Directory '%2' does not exist.")
114 .arg(QDir::toNativeSeparators(filePath), QDir::toNativeSeparators(p.path)))
116 validPackage =
false;
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)))
128 validPackage =
false;
213 const QString licensesDirPath = locateLicensesDir(p.path);
214 const QStringList licenseIds = extractLicenseIdsFromSPDXExpression(p.licenseId);
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";
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));
228 std::cerr << qPrintable(tr(
"Missing expected license file:")) << std::endl;
229 std::cerr << qPrintable(QDir::toNativeSeparators(licensesDirLocal.filePath(fileNameLocal)))
231 if (!licensesDirPath.isEmpty()) {
232 std::cerr << qPrintable(tr(
"or\n %1").arg(
233 QDir::toNativeSeparators(licensesDir.filePath(fileName))))
273 bool validPackage =
true;
274 const QString directory = QFileInfo(filePath).absolutePath();
275 p.filePath = QFileInfo(filePath).absoluteFilePath();
278 for (
auto iter = object.constBegin(); iter != object.constEnd(); ++iter) {
279 const QString key = iter.key();
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) {
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;
290 const QString value = iter.value().toString();
291 if (key ==
"Name"_L1) {
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);
300 p.files = maybeStringList.value();
301 }
else if (jsonValue.isString()) {
303 p.files = value.simplified().split(QLatin1Char(
' '), Qt::SkipEmptyParts);
306 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings as value "
307 "of Files.").arg(QDir::toNativeSeparators(filePath)))
309 validPackage =
false;
313 }
else if (key ==
"Comment"_L1) {
318 }
else if (key ==
"Id"_L1) {
320 }
else if (key ==
"Origin"_L1) {
321 if (value ==
"InSource"_L1) {
323 }
else if (value ==
"Provisioned"_L1) {
326 std::cerr << qPrintable(tr(
"File %1: Expected either 'InSource' or 'Provisioned'"
327 " as value of 'Origin'.")
328 .arg(QDir::toNativeSeparators(filePath)))
330 validPackage =
false;
333 }
else if (key ==
"Homepage"_L1) {
335 }
else if (key ==
"Version"_L1) {
337 }
else if (key ==
"DownloadLocation"_L1) {
338 p.downloadLocation = value;
339 }
else if (key ==
"License"_L1) {
341 }
else if (key ==
"LicenseId"_L1) {
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());
349 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings in %2.")
350 .arg(QDir::toNativeSeparators(filePath), key))
352 validPackage =
false;
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()) {
362 auto maybeString = arrayToMultiLineString(jsonValue);
364 p.copyright = maybeString.value();
365 }
else if (jsonValue.isString()) {
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;
377 }
else if (key ==
"CPE"_L1) {
378 const QJsonValueConstRef jsonValue = iter.value();
379 if (!handleStringOrStringArrayJsonKey(p.cpeList, key, jsonValue, filePath, logLevel)) {
380 validPackage =
false;
383 }
else if (key ==
"PURL"_L1) {
384 const QJsonValueConstRef jsonValue = iter.value();
385 if (!handleStringOrStringArrayJsonKey(p.purlList, key, jsonValue, filePath, logLevel)) {
386 validPackage =
false;
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) {
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))
404 validPackage =
false;
408 }
else if (key ==
"QtParts"_L1) {
409 auto parts = toStringList(iter.value());
412 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings in %2.")
413 .arg(QDir::toNativeSeparators(filePath), key))
416 validPackage =
false;
419 p.qtParts = parts.value();
422 std::cerr << qPrintable(tr(
"File %1: Unknown key %2.").arg(
423 QDir::toNativeSeparators(filePath), key)) <<
std::endl;
425 validPackage =
false;
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()) {
437 std::cerr << qPrintable(
438 tr(
"File %1: $<VERSION> used but 'Version' is not set.")
439 .arg(QDir::toNativeSeparators(filePath)))
442 validPackage =
false;
445 s.replace(versionVar, p.version);
446 s.replace(versionDashedVar,
447 QString(p.version).replace(u'.', u'-'));
450 auto replaceInList = [&](QStringList &list) {
451 for (QString &s : list)
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);
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;
472 p.copyrightFileContents = QString::fromUtf8(file.readAll());
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)));
486 validPackage =
false;
488 p.licenseFilesContents << QString::fromUtf8(file.readAll()).trimmed();
500 const QString directory = QFileInfo(filePath).absolutePath();
503 QHash<QString, QString> fields;
505 QTextStream in(&file);
506 while (!in.atEnd()) {
507 QString line = in.readLine().trimmed();
508 QStringList parts = line.split(u":"_s);
510 if (parts.size() < 2)
513 QString key = parts.at(0);
515 QString value = parts.join(QString()).trimmed();
519 if (line ==
"Description:"_L1) {
520 while (!in.atEnd()) {
521 QString line = in.readLine().trimmed();
523 if (line.startsWith(
"Local Modifications:"_L1))
526 fields[key] += line + u"\n"_s;
536 QString shortName = fields.contains(
"Short Name"_L1)
537 ? fields[
"Short Name"_L1]
539 QString version = fields[u"Version"_s];
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'))
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();
553 QString licenseFile = fields[u"License File"_s];
554 if (licenseFile != QString() && licenseFile !=
"NOT_SHIPPED"_L1) {
555 p.licenseFiles = QStringList(QDir(directory).absoluteFilePath(licenseFile));
558 QDir dir = directory;
560 dir.setNameFilters({ u"LICENSE"_s, u"COPYING"_s });
561 dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
563 const QFileInfoList entries = dir.entryInfoList();
564 if (!entries.empty())
565 p.licenseFiles = QStringList(entries.at(0).absoluteFilePath());
569 Q_UNUSED(validatePackage(p, {}, logLevel));
597std::optional<QList<Package>> readFile(
const QString &filePath,
LogLevel logLevel)
599 QList<Package> packages;
600 bool errorsFound =
false;
603 std::cerr << qPrintable(tr(
"Reading file %1...").arg(
604 QDir::toNativeSeparators(filePath))) <<
std::endl;
606 QFile file(filePath);
607 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
609 std::cerr << qPrintable(tr(
"Could not open file %1.").arg(
610 QDir::toNativeSeparators(file.fileName()))) <<
std::endl;
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()) {
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)))
631 if (document.isObject()) {
633 readPackage(document.object(), file.fileName(), logLevel);
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()) {
645 readPackage(value.toObject(), file.fileName(), logLevel);
653 std::cerr << qPrintable(tr(
"File %1: Expecting JSON object in array.")
654 .arg(QDir::toNativeSeparators(file.fileName())))
662 std::cerr << qPrintable(tr(
"File %1: Expecting JSON object in array.").arg(
663 QDir::toNativeSeparators(file.fileName()))) <<
std::endl;
667 }
else if (filePath.endsWith(
".chromium"_L1)) {
668 Package chromiumPackage = parseChromiumFile(file, filePath, logLevel);
669 if (!chromiumPackage.name.isEmpty())
670 packages << chromiumPackage;
673 std::cerr << qPrintable(tr(
"File %1: Unsupported file type.")
674 .arg(QDir::toNativeSeparators(file.fileName())))
689 QList<Package> packages;
690 bool errorsFound =
false;
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;
700 dir.setNameFilters(nameFilters);
701 dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Files);
703 const QFileInfoList entries = dir.entryInfoList();
704 for (
const QFileInfo &info : entries) {
706 std::optional<QList<Package>> ps =
707 scanDirectory(info.filePath(), inputFormats, logLevel);
713 std::optional p = readFile(info.filePath(), logLevel);