diff options
author | Joerg Bornemann <joerg.bornemann@qt.io> | 2022-10-14 15:21:53 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2022-11-18 08:06:02 +0000 |
commit | 3afc776eadd3e273740b7471320a94df1e33a96e (patch) | |
tree | ec0ba4bf979676a08ae4f442cbfa7c8c879b527d /src/qtattributionsscanner/scanner.cpp | |
parent | d40bda52ed9943e77b14f24bd8e5675ee5439843 (diff) | |
download | qttools-3afc776eadd3e273740b7471320a94df1e33a96e.tar.gz |
qtattributionsscanner: Read license files from LICENSES directory
If the 'LicenseFile[s]' property is empty in the qt_attributions.json
file, we now extract the SPDX license identifiers from the SPDX license
expression in the LicenseId property.
The extracted SPDX license identifiers are used to locate license files
in the LICENSES directory up in the directory hierarchy. If we
encounter a license ID without matching license file, we print an error
message.
This enables us to deduplicate license files in our repositories.
Fixes: QTBUG-104126
Change-Id: I38b281c97e039a8158e143ffa16ba1966713d030
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Kai Koehne <kai.koehne@qt.io>
(cherry picked from commit c49c1b7a9310325f899122511e450875a14bfba8)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'src/qtattributionsscanner/scanner.cpp')
-rw-r--r-- | src/qtattributionsscanner/scanner.cpp | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/src/qtattributionsscanner/scanner.cpp b/src/qtattributionsscanner/scanner.cpp index 536e42ae8..9f1d98a87 100644 --- a/src/qtattributionsscanner/scanner.cpp +++ b/src/qtattributionsscanner/scanner.cpp @@ -14,6 +14,8 @@ #include <iostream> +using namespace Qt::Literals::StringLiterals; + namespace Scanner { static void missingPropertyWarning(const QString &filePath, const QString &property) @@ -90,6 +92,79 @@ static std::optional<QStringList> toStringList(const QJsonValue &value) return result; } +// Extracts SPDX license ids from a SPDX license expression. +// For "(BSD-3-Clause AND BeerWare)" this function returns { "BSD-3-Clause", "BeerWare" }. +static QStringList extractLicenseIdsFromSPDXExpression(QString expression) +{ + const QStringList spdxOperators = { + u"AND"_s, + u"OR"_s, + u"WITH"_s + }; + + // Replace parentheses with spaces. We're not interested in grouping. + const QRegularExpression parensRegex(u"[()]"_s); + expression.replace(parensRegex, u" "_s); + + // Split the string at space boundaries to extract tokens. + QStringList result; + for (const QString &token : expression.split(QLatin1Char(' '), Qt::SkipEmptyParts)) { + if (spdxOperators.contains(token)) + continue; + + // Remove the unary + operator, if present. + if (token.endsWith(QLatin1Char('+'))) + result.append(token.mid(0, token.length() - 1)); + else + result.append(token); + } + return result; +} + +// Starting at packageDir, look for a LICENSES subdirectory in the directory hierarchy upwards. +// Return a default-constructed QString if the directory was not found. +static QString locateLicensesDir(const QString &packageDir) +{ + static const QString licensesSubDir = u"LICENSES"_s; + QDir dir(packageDir); + while (true) { + if (dir.cd(licensesSubDir)) + return dir.path(); + if (dir.isRoot()) + break; + dir.cdUp(); + } + return {}; +} + +// Locates the license files that belong to the licenses mentioned in LicenseId and stores them in +// the specified package object. +static bool autoDetectLicenseFiles(Package &p) +{ + const QString licensesDirPath = locateLicensesDir(p.path); + const QStringList licenseIds = extractLicenseIdsFromSPDXExpression(p.licenseId); + if (!licenseIds.isEmpty() && licensesDirPath.isEmpty()) { + std::cerr << qPrintable(tr("LICENSES directory could not be located.")) << std::endl; + return false; + } + + bool success = true; + QDir licensesDir(licensesDirPath); + for (const QString &id : licenseIds) { + QString fileName = id + u".txt"; + if (licensesDir.exists(fileName)) { + p.licenseFiles.append(licensesDir.filePath(fileName)); + } else { + std::cerr << qPrintable(tr("Expected license file not found: %1").arg( + QDir::toNativeSeparators(licensesDir.filePath(fileName)))) + << std::endl; + success = false; + } + } + + return success; +} + // Transforms a JSON object into a Package object static std::optional<Package> readPackage(const QJsonObject &object, const QString &filePath, LogLevel logLevel) @@ -201,6 +276,9 @@ static std::optional<Package> readPackage(const QJsonObject &object, const QStri p.licenseFilesContents << QString::fromUtf8(file.readAll()).trimmed(); } + if (p.licenseFiles.isEmpty() && !autoDetectLicenseFiles(p)) + return std::nullopt; + if (!validatePackage(p, filePath, logLevel) || !validPackage) return std::nullopt; |