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);
47 }
else if (!p.id.isLower() || p.id.contains(
' '_L1)) {
48 if (logLevel != SilentLog)
49 std::cerr << qPrintable(tr(
"File %1: Value of 'Id' must be in lowercase and without spaces.")
50 .arg(QDir::toNativeSeparators(filePath))) << std::endl;
54 if (p.license.isEmpty()) {
55 if (logLevel != SilentLog)
56 missingPropertyWarning(filePath, u"License"_s);
60 if (!p.copyright.isEmpty() && !p.copyrightFile.isEmpty()) {
62 std::cerr << qPrintable(tr(
"File %1: Properties 'Copyright' and 'CopyrightFile' are "
63 "mutually exclusive.")
64 .arg(QDir::toNativeSeparators(filePath)))
71 if (logLevel != SilentLog)
72 missingPropertyWarning(filePath, u"DownloadLocation"_s);
76 for (
const QString &part : std::as_const(p.qtParts)) {
77 if (part !=
"examples"_L1 && part !=
"tests"_L1
78 && part !=
"tools"_L1 && part !=
"libs"_L1) {
80 if (logLevel != SilentLog) {
81 std::cerr << qPrintable(tr(
"File %1: Property 'QtPart' contains unknown element "
82 "'%2'. Valid entries are 'examples', 'tests', 'tools' "
84 QDir::toNativeSeparators(filePath), part))
91 if (!(checks & Check::Paths))
94 const QDir dir = p.path;
96 std::cerr << qPrintable(
97 tr(
"File %1: Directory '%2' does not exist.")
98 .arg(QDir::toNativeSeparators(filePath), QDir::toNativeSeparators(p.path)))
100 validPackage =
false;
102 for (
const QString &file : std::as_const(p.files)) {
103 if (!dir.exists(file)) {
104 if (logLevel != SilentLog) {
105 std::cerr << qPrintable(
106 tr(
"File %1: Path '%2' does not exist in directory '%3'.")
107 .arg(QDir::toNativeSeparators(filePath),
108 QDir::toNativeSeparators(file),
109 QDir::toNativeSeparators(p.path)))
112 validPackage =
false;
197 const QString licensesDirPath = locateLicensesDir(p.path);
198 const QStringList licenseIds = extractLicenseIdsFromSPDXExpression(p.licenseId);
201 QDir licensesDir(licensesDirPath);
202 QDir licensesDirLocal = p.path;
203 for (
const QString &id : licenseIds) {
204 QString fileName = id + u".txt";
205 QString fileNameLocal = u"LICENSE." + id + u".txt";
207 if (licensesDirLocal.exists(fileNameLocal)) {
208 p.licenseFiles.append(licensesDirLocal.filePath(fileNameLocal));
209 }
else if (licensesDir.exists(fileName)) {
210 p.licenseFiles.append(licensesDir.filePath(fileName));
212 std::cerr << qPrintable(tr(
"Missing expected license file:")) << std::endl;
213 std::cerr << qPrintable(QDir::toNativeSeparators(licensesDirLocal.filePath(fileNameLocal)))
215 if (!licensesDirPath.isEmpty()) {
216 std::cerr << qPrintable(tr(
"or\n %1").arg(
217 QDir::toNativeSeparators(licensesDir.filePath(fileName))))
257 bool validPackage =
true;
258 const QString directory = QFileInfo(filePath).absolutePath();
261 for (
auto iter = object.constBegin(); iter != object.constEnd(); ++iter) {
262 const QString key = iter.key();
264 if (!iter.value().isString() && key !=
"QtParts"_L1 && key !=
"SecurityCritical"_L1
265 && key !=
"Files"_L1 && key !=
"LicenseFiles"_L1 && key !=
"Comment"_L1
266 && key !=
"Copyright"_L1 && key !=
"CPE"_L1 && key !=
"PURL"_L1) {
267 if (logLevel != SilentLog)
268 std::cerr << qPrintable(tr(
"File %1: Expected JSON string as value of %2.").arg(
269 QDir::toNativeSeparators(filePath), key)) << std::endl;
270 validPackage =
false;
273 const QString value = iter.value().toString();
274 if (key ==
"Name"_L1) {
276 }
else if (key ==
"Path"_L1) {
277 p.path = QDir(directory).absoluteFilePath(value);
278 }
else if (key ==
"Files"_L1) {
279 QJsonValueConstRef jsonValue = iter.value();
280 if (jsonValue.isArray()) {
281 auto maybeStringList = toStringList(jsonValue);
283 p.files = maybeStringList.value();
284 }
else if (jsonValue.isString()) {
286 p.files = value.simplified().split(QLatin1Char(
' '), Qt::SkipEmptyParts);
289 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings as value "
290 "of Files.").arg(QDir::toNativeSeparators(filePath)))
292 validPackage =
false;
296 }
else if (key ==
"Comment"_L1) {
301 }
else if (key ==
"Id"_L1) {
303 }
else if (key ==
"Homepage"_L1) {
305 }
else if (key ==
"Version"_L1) {
307 }
else if (key ==
"DownloadLocation"_L1) {
308 p.downloadLocation = value;
309 }
else if (key ==
"License"_L1) {
311 }
else if (key ==
"LicenseId"_L1) {
313 }
else if (key ==
"LicenseFile"_L1) {
314 p.licenseFiles = QStringList(QDir(directory).absoluteFilePath(value));
315 }
else if (key ==
"LicenseFiles"_L1) {
316 auto strings = toStringList(iter.value());
318 if (logLevel != SilentLog)
319 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings in %2.")
320 .arg(QDir::toNativeSeparators(filePath), key))
322 validPackage =
false;
325 const QDir dir(directory);
326 for (
const auto &iter : std::as_const(strings.value()))
327 p.licenseFiles.push_back(dir.absoluteFilePath(iter));
328 }
else if (key ==
"Copyright"_L1) {
329 QJsonValueConstRef jsonValue = iter.value();
330 if (jsonValue.isArray()) {
332 auto maybeString = arrayToMultiLineString(jsonValue);
334 p.copyright = maybeString.value();
335 }
else if (jsonValue.isString()) {
340 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings or "
341 "string as value of %2.").arg(
342 QDir::toNativeSeparators(filePath), key)) << std::endl;
343 validPackage =
false;
347 }
else if (key ==
"CPE"_L1) {
348 const QJsonValueConstRef jsonValue = iter.value();
349 if (!handleStringOrStringArrayJsonKey(p.cpeList, key, jsonValue, filePath, logLevel)) {
350 validPackage =
false;
353 }
else if (key ==
"PURL"_L1) {
354 const QJsonValueConstRef jsonValue = iter.value();
355 if (!handleStringOrStringArrayJsonKey(p.purlList, key, jsonValue, filePath, logLevel)) {
356 validPackage =
false;
359 }
else if (key ==
"CopyrightFile"_L1) {
360 p.copyrightFile = QDir(directory).absoluteFilePath(value);
361 }
else if (key ==
"PackageComment"_L1) {
362 p.packageComment = value;
363 }
else if (key ==
"QDocModule"_L1) {
364 p.qdocModule = value;
365 }
else if (key ==
"Description"_L1) {
366 p.description = value;
367 }
else if (key ==
"QtUsage"_L1) {
369 }
else if (key ==
"SecurityCritical"_L1) {
370 if (!iter.value().isBool()) {
371 std::cerr << qPrintable(tr(
"File %1: Expected JSON boolean in %2.")
372 .arg(QDir::toNativeSeparators(filePath), key))
374 validPackage =
false;
378 }
else if (key ==
"QtParts"_L1) {
379 auto parts = toStringList(iter.value());
382 std::cerr << qPrintable(tr(
"File %1: Expected JSON array of strings in %2.")
383 .arg(QDir::toNativeSeparators(filePath), key))
386 validPackage =
false;
389 p.qtParts = parts.value();
392 std::cerr << qPrintable(tr(
"File %1: Unknown key %2.").arg(
393 QDir::toNativeSeparators(filePath), key)) << std::endl;
395 validPackage =
false;
399 if (!p.copyrightFile.isEmpty()) {
400 QFile file(p.copyrightFile);
401 if (!file.open(QIODevice::ReadOnly)) {
402 std::cerr << qPrintable(tr(
"File %1: Cannot open 'CopyrightFile' %2.\n")
403 .arg(QDir::toNativeSeparators(filePath),
404 QDir::toNativeSeparators(p.copyrightFile)));
405 validPackage =
false;
407 p.copyrightFileContents = QString::fromUtf8(file.readAll());
413 for (
const QString &licenseFile : std::as_const(p.licenseFiles)) {
414 QFile file(licenseFile);
415 if (!file.open(QIODevice::ReadOnly)) {
416 if (logLevel != SilentLog) {
417 std::cerr << qPrintable(tr(
"File %1: Cannot open 'LicenseFile' %2.\n")
418 .arg(QDir::toNativeSeparators(filePath),
419 QDir::toNativeSeparators(licenseFile)));
421 validPackage =
false;
423 p.licenseFilesContents << QString::fromUtf8(file.readAll()).trimmed();
426 if (!validatePackage(p, filePath, checks, logLevel) || !validPackage)
435 const QString directory = QFileInfo(filePath).absolutePath();
438 QHash<QString, QString> fields;
440 QTextStream in(&file);
441 while (!in.atEnd()) {
442 QString line = in.readLine().trimmed();
443 QStringList parts = line.split(u":"_s);
445 if (parts.size() < 2)
448 QString key = parts.at(0);
450 QString value = parts.join(QString()).trimmed();
454 if (line ==
"Description:"_L1) {
455 while (!in.atEnd()) {
456 QString line = in.readLine().trimmed();
458 if (line.startsWith(
"Local Modifications:"_L1))
461 fields[key] += line + u"\n"_s;
471 QString shortName = fields.contains(
"Short Name"_L1)
472 ? fields[
"Short Name"_L1]
474 QString version = fields[u"Version"_s];
476 p.id = u"chromium-"_s + shortName.toLower().replace(QChar::Space, u"-"_s);
477 p.name = fields[u"Name"_s];
478 if (version != QLatin1Char(
'0'))
480 p.license = fields[u"License"_s];
481 p.homepage = fields[u"URL"_s];
482 p.qdocModule = u"qtwebengine"_s;
483 p.qtUsage = u"Used in Qt WebEngine"_s;
484 p.description = fields[u"Description"_s].trimmed();
487 QString licenseFile = fields[u"License File"_s];
488 if (licenseFile != QString() && licenseFile !=
"NOT_SHIPPED"_L1) {
489 p.licenseFiles = QStringList(QDir(directory).absoluteFilePath(licenseFile));
492 QDir dir = directory;
494 dir.setNameFilters({ u"LICENSE"_s, u"COPYING"_s });
495 dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
497 const QFileInfoList entries = dir.entryInfoList();
498 if (!entries.empty())
499 p.licenseFiles = QStringList(entries.at(0).absoluteFilePath());
503 Q_UNUSED(validatePackage(p, filePath, {}, logLevel));
531std::optional<QList<Package>> readFile(
const QString &filePath, Checks checks,
LogLevel logLevel)
533 QList<Package> packages;
534 bool errorsFound =
false;
537 std::cerr << qPrintable(tr(
"Reading file %1...").arg(
538 QDir::toNativeSeparators(filePath))) << std::endl;
540 QFile file(filePath);
541 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
542 if (logLevel != SilentLog)
543 std::cerr << qPrintable(tr(
"Could not open file %1.").arg(
544 QDir::toNativeSeparators(file.fileName()))) << std::endl;
548 if (filePath.endsWith(
".json"_L1)) {
549 QJsonParseError jsonParseError;
550 const QByteArray content = file.readAll();
551 const QJsonDocument document = QJsonDocument::fromJson(content, &jsonParseError);
552 if (document.isNull()) {
554 const CursorPosition pos = mapFromOffset(content, jsonParseError.offset);
555 std::cerr << qPrintable(tr(
"Could not parse file %1: %2 at line %3, column %4")
556 .arg(QDir::toNativeSeparators(file.fileName()),
557 jsonParseError.errorString(),
558 QString::number(pos.line),
559 QString::number(pos.column)))
565 if (document.isObject()) {
567 readPackage(document.object(), file.fileName(), checks, logLevel);
573 }
else if (document.isArray()) {
574 QJsonArray array = document.array();
575 for (
int i = 0, size = array.size(); i < size; ++i) {
576 QJsonValue value = array.at(i);
577 if (value.isObject()) {
579 readPackage(value.toObject(), file.fileName(), checks, logLevel);
587 std::cerr << qPrintable(tr(
"File %1: Expecting JSON object in array.")
588 .arg(QDir::toNativeSeparators(file.fileName())))
596 std::cerr << qPrintable(tr(
"File %1: Expecting JSON object in array.").arg(
597 QDir::toNativeSeparators(file.fileName()))) << std::endl;
601 }
else if (filePath.endsWith(
".chromium"_L1)) {
602 Package chromiumPackage = parseChromiumFile(file, filePath, logLevel);
603 if (!chromiumPackage.name.isEmpty())
604 packages << chromiumPackage;
607 std::cerr << qPrintable(tr(
"File %1: Unsupported file type.")
608 .arg(QDir::toNativeSeparators(file.fileName())))
623 QList<Package> packages;
624 bool errorsFound =
false;
626 QStringList nameFilters = QStringList();
627 if (inputFormats & InputFormat::QtAttributions)
628 nameFilters << u"qt_attribution.json"_s;
629 if (inputFormats & InputFormat::ChromiumAttributions)
630 nameFilters << u"README.chromium"_s;
631 if (qEnvironmentVariableIsSet(
"QT_ATTRIBUTIONSSCANNER_TEST"))
632 nameFilters << u"qt_attribution_test.json"_s << u"README_test.chromium"_s;
634 dir.setNameFilters(nameFilters);
635 dir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Files);
637 const QFileInfoList entries = dir.entryInfoList();
638 for (
const QFileInfo &info : entries) {
640 std::optional<QList<Package>> ps =
641 scanDirectory(info.filePath(), inputFormats, checks, logLevel);
647 std::optional p = readFile(info.filePath(), checks, logLevel);