diff options
Diffstat (limited to 'src/tools/rcc/rcc.cpp')
-rw-r--r-- | src/tools/rcc/rcc.cpp | 121 |
1 files changed, 101 insertions, 20 deletions
diff --git a/src/tools/rcc/rcc.cpp b/src/tools/rcc/rcc.cpp index e461ab6294..06f9ae1015 100644 --- a/src/tools/rcc/rcc.cpp +++ b/src/tools/rcc/rcc.cpp @@ -1,14 +1,16 @@ // Copyright (C) 2018 The Qt Company Ltd. // Copyright (C) 2018 Intel Corporation. +// Copyright (C) 2024 Christoph Cullmann <christoph@cullmann.io> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "rcc.h" #include <qbytearray.h> +#include <qcryptographichash.h> #include <qdatetime.h> #include <qdebug.h> #include <qdir.h> -#include <qdiriterator.h> +#include <qdirlisting.h> #include <qfile.h> #include <qiodevice.h> #include <qlocale.h> @@ -21,7 +23,7 @@ # include <zstd.h> #endif -// Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc.cpp) +// Note: A copy of this file is used in Qt Widgets Designer (qttools/src/designer/src/lib/shared/rcc.cpp) QT_BEGIN_NAMESPACE @@ -90,8 +92,28 @@ public: QString resourceName() const; + struct DeduplicationKey { + RCCResourceLibrary::CompressionAlgorithm compressAlgo; + int compressLevel; + int compressThreshold; + QByteArray hash; + + bool operator==(const DeduplicationKey &other) const + { + return compressAlgo == other.compressAlgo && + compressLevel == other.compressLevel && + compressThreshold == other.compressThreshold && + hash == other.hash; + } + }; + + typedef QMultiHash<DeduplicationKey, RCCFileInfo*> DeduplicationMultiHash; + public: - qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage); + qint64 writeDataBlob(RCCResourceLibrary &lib, + qint64 offset, + DeduplicationMultiHash &dedupByContent, + QString *errorMessage); qint64 writeDataName(RCCResourceLibrary &, qint64 offset); void writeDataInfo(RCCResourceLibrary &lib); @@ -114,6 +136,11 @@ public: qint64 m_childOffset = 0; }; +static size_t qHash(const RCCFileInfo::DeduplicationKey &key, size_t seed) noexcept +{ + return qHashMulti(seed, key.compressAlgo, key.compressLevel, key.compressThreshold, key.hash); +} + RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language, QLocale::Territory territory, uint flags, RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, @@ -217,8 +244,10 @@ void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) } } -qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, - QString *errorMessage) +qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, + qint64 offset, + DeduplicationMultiHash &dedupByContent, + QString *errorMessage) { const bool text = lib.m_format == RCCResourceLibrary::C_Code; const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; @@ -231,14 +260,38 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QByteArray data; if (!m_isEmpty) { - //find the data to be written - QFile file(m_fileInfo.absoluteFilePath()); + // find the data to be written + const QString absoluteFilePath = m_fileInfo.absoluteFilePath(); + QFile file(absoluteFilePath); if (!file.open(QFile::ReadOnly)) { - *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); + *errorMessage = msgOpenReadFailed(absoluteFilePath, file.errorString()); return 0; } - data = file.readAll(); + + // de-duplicate the same file content, we can re-use already written data + // we only do that if we have the same compression settings + const QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha256); + const DeduplicationKey key{m_compressAlgo, m_compressLevel, m_compressThreshold, hash}; + const QList<RCCFileInfo *> potentialCandidates = dedupByContent.values(key); + for (const RCCFileInfo *candidate : potentialCandidates) { + // check real content, we can have collisions + QFile candidateFile(candidate->m_fileInfo.absoluteFilePath()); + if (!candidateFile.open(QFile::ReadOnly)) { + *errorMessage = msgOpenReadFailed(candidate->m_fileInfo.absoluteFilePath(), + candidateFile.errorString()); + return 0; + } + if (data != candidateFile.readAll()) { + continue; + } + // just remember the offset & flags with final compression state + // of the already written data and be done + m_dataOffset = candidate->m_dataOffset; + m_flags = candidate->m_flags; + return offset; + } + dedupByContent.insert(key, this); } // Check if compression is useful for this file @@ -321,7 +374,7 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, // some info if (text || pass1) { lib.writeString(" // "); - lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit()); + lib.writeByteArray(m_fileInfo.fileName().toLocal8Bit()); lib.writeString("\n "); } @@ -512,6 +565,12 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, reader.raiseError("expected <RCC> tag"_L1); else tokens.push(RccTag); + } else if (reader.name() == m_strings.TAG_LEGAL) { + if (tokens.isEmpty() || tokens.top() != RccTag) { + reader.raiseError("unexpected <legal> tag"_L1); + } else { + m_legal = reader.readElementText().trimmed(); + } } else if (reader.name() == m_strings.TAG_RESOURCE) { if (tokens.isEmpty() || tokens.top() != RccTag) { reader.raiseError("unexpected <RESOURCE> tag"_L1); @@ -634,12 +693,12 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, alias += slash; QStringList filePaths; - QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); - while (it.hasNext()) { - it.next(); - if (it.fileName() == "."_L1 || it.fileName() == ".."_L1) + using F = QDirListing::IteratorFlag; + for (const auto &entry : QDirListing(dir, F::FollowSymlinks | F::Recursive)) { + const QString &fileName = entry.fileName(); + if (fileName == "."_L1 || fileName == ".."_L1) continue; - filePaths.append(it.filePath()); + filePaths.emplace_back(entry.filePath()); } // make rcc output deterministic @@ -929,13 +988,17 @@ bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIO m_errorDevice->write("No data signature found\n"); return false; } + + if (c != pattern[i]) { + for (int k = 0; k < i; ++k) + outDevice.putChar(pattern[k]); + i = 0; + } + if (c == pattern[i]) { ++i; } else { - for (int k = 0; k < i; ++k) - outDevice.putChar(pattern[k]); outDevice.putChar(c); - i = 0; } } @@ -1083,20 +1146,34 @@ void RCCResourceLibrary::writeNumber8(quint64 number) bool RCCResourceLibrary::writeHeader() { + auto writeCopyright = [this](QByteArrayView prefix) { + const QStringList lines = m_legal.split(u'\n', Qt::SkipEmptyParts); + for (const QString &line : lines) { + write(prefix.data(), prefix.size()); + writeString(line.toUtf8().trimmed()); + writeChar('\n'); + } + }; switch (m_format) { case C_Code: case Pass1: writeString("/****************************************************************************\n"); writeString("** Resource object code\n"); + writeCopyright("** "); writeString("**\n"); writeString("** Created by: The Resource Compiler for Qt version "); writeByteArray(QT_VERSION_STR); writeString("\n**\n"); writeString("** WARNING! All changes made in this file will be lost!\n"); writeString( "*****************************************************************************/\n\n"); + writeString("#ifdef _MSC_VER\n" + "// disable informational message \"function ... selected for automatic inline expansion\"\n" + "#pragma warning (disable: 4711)\n" + "#endif\n\n"); break; case Python_Code: writeString("# Resource object code (Python 3)\n"); + writeCopyright("# "); writeString("# Created by: object code\n"); writeString("# Created by: The Resource Compiler for Qt version "); writeByteArray(QT_VERSION_STR); @@ -1144,6 +1221,7 @@ bool RCCResourceLibrary::writeDataBlobs() QStack<RCCFileInfo*> pending; pending.push(m_root); qint64 offset = 0; + RCCFileInfo::DeduplicationMultiHash dedupByContent; QString errorMessage; while (!pending.isEmpty()) { RCCFileInfo *file = pending.pop(); @@ -1152,7 +1230,8 @@ bool RCCResourceLibrary::writeDataBlobs() if (child->m_flags & RCCFileInfo::Directory) pending.push(child); else { - offset = child->writeDataBlob(*this, offset, &errorMessage); + offset = child->writeDataBlob(*this, offset, + dedupByContent, &errorMessage); if (offset == 0) { m_errorDevice->write(errorMessage.toUtf8()); return false; @@ -1374,7 +1453,9 @@ bool RCCResourceLibrary::writeInitializer() "# define QT_RCC_MANGLE_NAMESPACE(name) name\n" "#endif\n\n"); - writeString("#ifdef QT_NAMESPACE\n" + writeString("#if defined(QT_INLINE_NAMESPACE)\n" + "inline namespace QT_NAMESPACE {\n" + "#elif defined(QT_NAMESPACE)\n" "namespace QT_NAMESPACE {\n" "#endif\n\n"); } |