summaryrefslogtreecommitdiffstats
path: root/src/tools/rcc/rcc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rcc/rcc.cpp')
-rw-r--r--src/tools/rcc/rcc.cpp121
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");
}