| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 The Qt Company Ltd. |
| ** Copyright (C) 2018 Intel Corporation. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "rcc.h" |
| |
| #include <qbytearray.h> |
| #include <qdatetime.h> |
| #include <qdebug.h> |
| #include <qdir.h> |
| #include <qdiriterator.h> |
| #include <qfile.h> |
| #include <qiodevice.h> |
| #include <qlocale.h> |
| #include <qregexp.h> |
| #include <qstack.h> |
| #include <qxmlstream.h> |
| |
| #include <algorithm> |
| |
| #if QT_CONFIG(zstd) |
| # include <zstd.h> |
| #endif |
| |
| // Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc.cpp) |
| |
| QT_BEGIN_NAMESPACE |
| |
| enum { |
| CONSTANT_USENAMESPACE = 1, |
| CONSTANT_COMPRESSLEVEL_DEFAULT = -1, |
| CONSTANT_ZSTDCOMPRESSLEVEL_CHECK = 1, // Zstd level to check if compressing is a good idea |
| CONSTANT_ZSTDCOMPRESSLEVEL_STORE = 14, // Zstd level to actually store the data |
| CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 |
| }; |
| |
| #if QT_CONFIG(zstd) && QT_VERSION >= QT_VERSION_CHECK(6,0,0) |
| # define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zstd |
| #elif !defined(QT_NO_COMPRESS) |
| # define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::Zlib |
| #else |
| # define CONSTANT_COMPRESSALGO_DEFAULT RCCResourceLibrary::CompressionAlgorithm::None |
| #endif |
| |
| void RCCResourceLibrary::write(const char *str, int len) |
| { |
| int n = m_out.size(); |
| m_out.resize(n + len); |
| memcpy(m_out.data() + n, str, len); |
| } |
| |
| void RCCResourceLibrary::writeByteArray(const QByteArray &other) |
| { |
| if (m_format == Pass2) { |
| m_outDevice->write(other); |
| } else { |
| m_out.append(other); |
| } |
| } |
| |
| static inline QString msgOpenReadFailed(const QString &fname, const QString &why) |
| { |
| return QString::fromLatin1("Unable to open %1 for reading: %2\n").arg(fname, why); |
| } |
| |
| |
| /////////////////////////////////////////////////////////// |
| // |
| // RCCFileInfo |
| // |
| /////////////////////////////////////////////////////////// |
| |
| class RCCFileInfo |
| { |
| public: |
| enum Flags |
| { |
| // must match qresource.cpp |
| NoFlags = 0x00, |
| Compressed = 0x01, |
| Directory = 0x02, |
| CompressedZstd = 0x04 |
| }; |
| |
| RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(), |
| QLocale::Language language = QLocale::C, |
| QLocale::Country country = QLocale::AnyCountry, |
| uint flags = NoFlags, |
| RCCResourceLibrary::CompressionAlgorithm compressAlgo = CONSTANT_COMPRESSALGO_DEFAULT, |
| int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT, |
| int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT); |
| ~RCCFileInfo(); |
| |
| QString resourceName() const; |
| |
| public: |
| qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage); |
| qint64 writeDataName(RCCResourceLibrary &, qint64 offset); |
| void writeDataInfo(RCCResourceLibrary &lib); |
| |
| int m_flags; |
| QString m_name; |
| QLocale::Language m_language; |
| QLocale::Country m_country; |
| QFileInfo m_fileInfo; |
| RCCFileInfo *m_parent; |
| QHash<QString, RCCFileInfo*> m_children; |
| RCCResourceLibrary::CompressionAlgorithm m_compressAlgo; |
| int m_compressLevel; |
| int m_compressThreshold; |
| |
| qint64 m_nameOffset; |
| qint64 m_dataOffset; |
| qint64 m_childOffset; |
| }; |
| |
| RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, |
| QLocale::Language language, QLocale::Country country, uint flags, |
| RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, int compressThreshold) |
| { |
| m_name = name; |
| m_fileInfo = fileInfo; |
| m_language = language; |
| m_country = country; |
| m_flags = flags; |
| m_parent = 0; |
| m_nameOffset = 0; |
| m_dataOffset = 0; |
| m_childOffset = 0; |
| m_compressAlgo = compressAlgo; |
| m_compressLevel = compressLevel; |
| m_compressThreshold = compressThreshold; |
| } |
| |
| RCCFileInfo::~RCCFileInfo() |
| { |
| qDeleteAll(m_children); |
| } |
| |
| QString RCCFileInfo::resourceName() const |
| { |
| QString resource = m_name; |
| for (RCCFileInfo *p = m_parent; p; p = p->m_parent) |
| resource = resource.prepend(p->m_name + QLatin1Char('/')); |
| return QLatin1Char(':') + resource; |
| } |
| |
| void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) |
| { |
| const bool text = lib.m_format == RCCResourceLibrary::C_Code; |
| const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; |
| const bool python = lib.m_format == RCCResourceLibrary::Python3_Code |
| || lib.m_format == RCCResourceLibrary::Python2_Code; |
| //some info |
| if (text || pass1) { |
| if (m_language != QLocale::C) { |
| lib.writeString(" // "); |
| lib.writeByteArray(resourceName().toLocal8Bit()); |
| lib.writeString(" ["); |
| lib.writeByteArray(QByteArray::number(m_country)); |
| lib.writeString("::"); |
| lib.writeByteArray(QByteArray::number(m_language)); |
| lib.writeString("[\n "); |
| } else { |
| lib.writeString(" // "); |
| lib.writeByteArray(resourceName().toLocal8Bit()); |
| lib.writeString("\n "); |
| } |
| } |
| |
| //pointer data |
| if (m_flags & RCCFileInfo::Directory) { |
| // name offset |
| lib.writeNumber4(m_nameOffset); |
| |
| // flags |
| lib.writeNumber2(m_flags); |
| |
| // child count |
| lib.writeNumber4(m_children.size()); |
| |
| // first child offset |
| lib.writeNumber4(m_childOffset); |
| } else { |
| // name offset |
| lib.writeNumber4(m_nameOffset); |
| |
| // flags |
| lib.writeNumber2(m_flags); |
| |
| // locale |
| lib.writeNumber2(m_country); |
| lib.writeNumber2(m_language); |
| |
| //data offset |
| lib.writeNumber4(m_dataOffset); |
| } |
| if (text || pass1) |
| lib.writeChar('\n'); |
| else if (python) |
| lib.writeString("\\\n"); |
| |
| if (lib.formatVersion() >= 2) { |
| // last modified time stamp |
| const QDateTime lastModified = m_fileInfo.lastModified(); |
| quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0); |
| static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong(); |
| if (sourceDate != 0) |
| lastmod = sourceDate; |
| static const quint64 sourceDate2 = 1000 * qgetenv("SOURCE_DATE_EPOCH").toULongLong(); |
| if (sourceDate2 != 0) |
| lastmod = sourceDate2; |
| lib.writeNumber8(lastmod); |
| if (text || pass1) |
| lib.writeChar('\n'); |
| else if (python) |
| lib.writeString("\\\n"); |
| } |
| } |
| |
| qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, |
| QString *errorMessage) |
| { |
| const bool text = lib.m_format == RCCResourceLibrary::C_Code; |
| const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; |
| const bool pass2 = lib.m_format == RCCResourceLibrary::Pass2; |
| const bool binary = lib.m_format == RCCResourceLibrary::Binary; |
| const bool python = lib.m_format == RCCResourceLibrary::Python3_Code |
| || lib.m_format == RCCResourceLibrary::Python2_Code; |
| |
| //capture the offset |
| m_dataOffset = offset; |
| |
| //find the data to be written |
| QFile file(m_fileInfo.absoluteFilePath()); |
| if (!file.open(QFile::ReadOnly)) { |
| *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); |
| return 0; |
| } |
| QByteArray data = file.readAll(); |
| |
| // Check if compression is useful for this file |
| if (data.size() != 0) { |
| #if QT_CONFIG(zstd) |
| if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { |
| m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd; |
| m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental |
| } |
| if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd) { |
| if (lib.m_zstdCCtx == nullptr) |
| lib.m_zstdCCtx = ZSTD_createCCtx(); |
| qsizetype size = data.size(); |
| size = ZSTD_COMPRESSBOUND(size); |
| |
| int compressLevel = m_compressLevel; |
| if (compressLevel < 0) |
| compressLevel = CONSTANT_ZSTDCOMPRESSLEVEL_CHECK; |
| |
| QByteArray compressed(size, Qt::Uninitialized); |
| char *dst = const_cast<char *>(compressed.constData()); |
| size_t n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, |
| data.constData(), data.size(), |
| compressLevel); |
| if (n * 100.0 < data.size() * 1.0 * (100 - m_compressThreshold) ) { |
| // compressing is worth it |
| if (m_compressLevel < 0) { |
| // heuristic compression, so recompress |
| n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, |
| data.constData(), data.size(), |
| CONSTANT_ZSTDCOMPRESSLEVEL_STORE); |
| } |
| if (ZSTD_isError(n)) { |
| QString msg = QString::fromLatin1("%1: error: compression with zstd failed: %2\n") |
| .arg(m_name, QString::fromUtf8(ZSTD_getErrorName(n))); |
| lib.m_errorDevice->write(msg.toUtf8()); |
| } else if (lib.verbose()) { |
| QString msg = QString::fromLatin1("%1: note: compressed using zstd (%2 -> %3)\n") |
| .arg(m_name).arg(data.size()).arg(n); |
| lib.m_errorDevice->write(msg.toUtf8()); |
| } |
| |
| lib.m_overallFlags |= CompressedZstd; |
| m_flags |= CompressedZstd; |
| data = std::move(compressed); |
| data.truncate(n); |
| } else if (lib.verbose()) { |
| QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); |
| lib.m_errorDevice->write(msg.toUtf8()); |
| } |
| } |
| #endif |
| #ifndef QT_NO_COMPRESS |
| if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { |
| m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib; |
| m_compressLevel = 9; |
| } |
| if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zlib) { |
| QByteArray compressed = |
| qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel); |
| |
| int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); |
| if (compressRatio >= m_compressThreshold) { |
| if (lib.verbose()) { |
| QString msg = QString::fromLatin1("%1: note: compressed using zlib (%2 -> %3)\n") |
| .arg(m_name).arg(data.size()).arg(compressed.size()); |
| lib.m_errorDevice->write(msg.toUtf8()); |
| } |
| data = compressed; |
| lib.m_overallFlags |= Compressed; |
| m_flags |= Compressed; |
| } else if (lib.verbose()) { |
| QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); |
| lib.m_errorDevice->write(msg.toUtf8()); |
| } |
| } |
| #endif // QT_NO_COMPRESS |
| } |
| |
| // some info |
| if (text || pass1) { |
| lib.writeString(" // "); |
| lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit()); |
| lib.writeString("\n "); |
| } |
| |
| // write the length |
| if (text || binary || pass2 || python) |
| lib.writeNumber4(data.size()); |
| if (text || pass1) |
| lib.writeString("\n "); |
| else if (python) |
| lib.writeString("\\\n"); |
| offset += 4; |
| |
| // write the payload |
| const char *p = data.constData(); |
| if (text || python) { |
| for (int i = data.size(), j = 0; --i >= 0; --j) { |
| lib.writeHex(*p++); |
| if (j == 0) { |
| if (text) |
| lib.writeString("\n "); |
| else |
| lib.writeString("\\\n"); |
| j = 16; |
| } |
| } |
| } else if (binary || pass2) { |
| lib.writeByteArray(data); |
| } |
| offset += data.size(); |
| |
| // done |
| if (text || pass1) |
| lib.writeString("\n "); |
| else if (python) |
| lib.writeString("\\\n"); |
| |
| return offset; |
| } |
| |
| qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) |
| { |
| const bool text = lib.m_format == RCCResourceLibrary::C_Code; |
| const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; |
| const bool python = lib.m_format == RCCResourceLibrary::Python3_Code |
| || lib.m_format == RCCResourceLibrary::Python2_Code; |
| |
| // capture the offset |
| m_nameOffset = offset; |
| |
| // some info |
| if (text || pass1) { |
| lib.writeString(" // "); |
| lib.writeByteArray(m_name.toLocal8Bit()); |
| lib.writeString("\n "); |
| } |
| |
| // write the length |
| lib.writeNumber2(m_name.length()); |
| if (text || pass1) |
| lib.writeString("\n "); |
| else if (python) |
| lib.writeString("\\\n"); |
| offset += 2; |
| |
| // write the hash |
| lib.writeNumber4(qt_hash(m_name)); |
| if (text || pass1) |
| lib.writeString("\n "); |
| else if (python) |
| lib.writeString("\\\n"); |
| offset += 4; |
| |
| // write the m_name |
| const QChar *unicode = m_name.unicode(); |
| for (int i = 0; i < m_name.length(); ++i) { |
| lib.writeNumber2(unicode[i].unicode()); |
| if ((text || pass1) && i % 16 == 0) |
| lib.writeString("\n "); |
| else if (python && i % 16 == 0) |
| lib.writeString("\\\n"); |
| } |
| offset += m_name.length()*2; |
| |
| // done |
| if (text || pass1) |
| lib.writeString("\n "); |
| else if (python) |
| lib.writeString("\\\n"); |
| |
| return offset; |
| } |
| |
| |
| /////////////////////////////////////////////////////////// |
| // |
| // RCCResourceLibrary |
| // |
| /////////////////////////////////////////////////////////// |
| |
| RCCResourceLibrary::Strings::Strings() : |
| TAG_RCC(QLatin1String("RCC")), |
| TAG_RESOURCE(QLatin1String("qresource")), |
| TAG_FILE(QLatin1String("file")), |
| ATTRIBUTE_LANG(QLatin1String("lang")), |
| ATTRIBUTE_PREFIX(QLatin1String("prefix")), |
| ATTRIBUTE_ALIAS(QLatin1String("alias")), |
| ATTRIBUTE_THRESHOLD(QLatin1String("threshold")), |
| ATTRIBUTE_COMPRESS(QLatin1String("compress")), |
| ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm")) |
| { |
| } |
| |
| RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion) |
| : m_root(0), |
| m_format(C_Code), |
| m_verbose(false), |
| m_compressionAlgo(CONSTANT_COMPRESSALGO_DEFAULT), |
| m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), |
| m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), |
| m_treeOffset(0), |
| m_namesOffset(0), |
| m_dataOffset(0), |
| m_overallFlags(0), |
| m_useNameSpace(CONSTANT_USENAMESPACE), |
| m_errorDevice(0), |
| m_outDevice(0), |
| m_formatVersion(formatVersion) |
| { |
| m_out.reserve(30 * 1000 * 1000); |
| #if QT_CONFIG(zstd) |
| m_zstdCCtx = nullptr; |
| #endif |
| } |
| |
| RCCResourceLibrary::~RCCResourceLibrary() |
| { |
| delete m_root; |
| #if QT_CONFIG(zstd) |
| ZSTD_freeCCtx(m_zstdCCtx); |
| #endif |
| } |
| |
| enum RCCXmlTag { |
| RccTag, |
| ResourceTag, |
| FileTag |
| }; |
| Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE); |
| |
| bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, |
| const QString &fname, QString currentPath, bool listMode) |
| { |
| Q_ASSERT(m_errorDevice); |
| const QChar slash = QLatin1Char('/'); |
| if (!currentPath.isEmpty() && !currentPath.endsWith(slash)) |
| currentPath += slash; |
| |
| QXmlStreamReader reader(inputDevice); |
| QStack<RCCXmlTag> tokens; |
| |
| QString prefix; |
| QLocale::Language language = QLocale::c().language(); |
| QLocale::Country country = QLocale::c().country(); |
| QString alias; |
| auto compressAlgo = m_compressionAlgo; |
| int compressLevel = m_compressLevel; |
| int compressThreshold = m_compressThreshold; |
| |
| while (!reader.atEnd()) { |
| QXmlStreamReader::TokenType t = reader.readNext(); |
| switch (t) { |
| case QXmlStreamReader::StartElement: |
| if (reader.name() == m_strings.TAG_RCC) { |
| if (!tokens.isEmpty()) |
| reader.raiseError(QLatin1String("expected <RCC> tag")); |
| else |
| tokens.push(RccTag); |
| } else if (reader.name() == m_strings.TAG_RESOURCE) { |
| if (tokens.isEmpty() || tokens.top() != RccTag) { |
| reader.raiseError(QLatin1String("unexpected <RESOURCE> tag")); |
| } else { |
| tokens.push(ResourceTag); |
| |
| QXmlStreamAttributes attributes = reader.attributes(); |
| language = QLocale::c().language(); |
| country = QLocale::c().country(); |
| |
| if (attributes.hasAttribute(m_strings.ATTRIBUTE_LANG)) { |
| QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString(); |
| QLocale lang = QLocale(attribute); |
| language = lang.language(); |
| if (2 == attribute.length()) { |
| // Language only |
| country = QLocale::AnyCountry; |
| } else { |
| country = lang.country(); |
| } |
| } |
| |
| prefix.clear(); |
| if (attributes.hasAttribute(m_strings.ATTRIBUTE_PREFIX)) |
| prefix = attributes.value(m_strings.ATTRIBUTE_PREFIX).toString(); |
| if (!prefix.startsWith(slash)) |
| prefix.prepend(slash); |
| if (!prefix.endsWith(slash)) |
| prefix += slash; |
| } |
| } else if (reader.name() == m_strings.TAG_FILE) { |
| if (tokens.isEmpty() || tokens.top() != ResourceTag) { |
| reader.raiseError(QLatin1String("unexpected <FILE> tag")); |
| } else { |
| tokens.push(FileTag); |
| |
| QXmlStreamAttributes attributes = reader.attributes(); |
| alias.clear(); |
| if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS)) |
| alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString(); |
| |
| compressAlgo = m_compressionAlgo; |
| compressLevel = m_compressLevel; |
| compressThreshold = m_compressThreshold; |
| |
| QString errorString; |
| if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO)) |
| compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString); |
| if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) { |
| QString value = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString(); |
| compressLevel = parseCompressionLevel(compressAlgo, value, &errorString); |
| } |
| |
| // Special case for -no-compress |
| if (m_compressLevel == -2) |
| compressAlgo = CompressionAlgorithm::None; |
| |
| if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD)) |
| compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt(); |
| |
| if (!errorString.isEmpty()) |
| reader.raiseError(errorString); |
| } |
| } else { |
| reader.raiseError(QString(QLatin1String("unexpected tag: %1")).arg(reader.name().toString())); |
| } |
| break; |
| |
| case QXmlStreamReader::EndElement: |
| if (reader.name() == m_strings.TAG_RCC) { |
| if (!tokens.isEmpty() && tokens.top() == RccTag) |
| tokens.pop(); |
| else |
| reader.raiseError(QLatin1String("unexpected closing tag")); |
| } else if (reader.name() == m_strings.TAG_RESOURCE) { |
| if (!tokens.isEmpty() && tokens.top() == ResourceTag) |
| tokens.pop(); |
| else |
| reader.raiseError(QLatin1String("unexpected closing tag")); |
| } else if (reader.name() == m_strings.TAG_FILE) { |
| if (!tokens.isEmpty() && tokens.top() == FileTag) |
| tokens.pop(); |
| else |
| reader.raiseError(QLatin1String("unexpected closing tag")); |
| } |
| break; |
| |
| case QXmlStreamReader::Characters: |
| if (reader.isWhitespace()) |
| break; |
| if (tokens.isEmpty() || tokens.top() != FileTag) { |
| reader.raiseError(QLatin1String("unexpected text")); |
| } else { |
| QString fileName = reader.text().toString(); |
| if (fileName.isEmpty()) { |
| const QString msg = QString::fromLatin1("RCC: Warning: Null node in XML of '%1'\n").arg(fname); |
| m_errorDevice->write(msg.toUtf8()); |
| } |
| |
| if (alias.isNull()) |
| alias = fileName; |
| |
| alias = QDir::cleanPath(alias); |
| while (alias.startsWith(QLatin1String("../"))) |
| alias.remove(0, 3); |
| alias = QDir::cleanPath(m_resourceRoot) + prefix + alias; |
| |
| QString absFileName = fileName; |
| if (QDir::isRelativePath(absFileName)) |
| absFileName.prepend(currentPath); |
| QFileInfo file(absFileName); |
| if (file.isDir()) { |
| QDir dir(file.filePath()); |
| if (!alias.endsWith(slash)) |
| alias += slash; |
| QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); |
| while (it.hasNext()) { |
| it.next(); |
| QFileInfo child(it.fileInfo()); |
| if (child.fileName() != QLatin1String(".") && child.fileName() != QLatin1String("..")) { |
| const bool arc = |
| addFile(alias + child.fileName(), |
| RCCFileInfo(child.fileName(), |
| child, |
| language, |
| country, |
| child.isDir() ? RCCFileInfo::Directory : RCCFileInfo::NoFlags, |
| compressAlgo, |
| compressLevel, |
| compressThreshold) |
| ); |
| if (!arc) |
| m_failedResources.push_back(child.fileName()); |
| } |
| } |
| } else if (listMode || file.isFile()) { |
| const bool arc = |
| addFile(alias, |
| RCCFileInfo(alias.section(slash, -1), |
| file, |
| language, |
| country, |
| RCCFileInfo::NoFlags, |
| compressAlgo, |
| compressLevel, |
| compressThreshold) |
| ); |
| if (!arc) |
| m_failedResources.push_back(absFileName); |
| } else if (file.exists()) { |
| m_failedResources.push_back(absFileName); |
| const QString msg = QString::fromLatin1("RCC: Error in '%1': Entry '%2' is neither a file nor a directory\n") |
| .arg(fname, fileName); |
| m_errorDevice->write(msg.toUtf8()); |
| return false; |
| } else { |
| m_failedResources.push_back(absFileName); |
| const QString msg = QString::fromLatin1("RCC: Error in '%1': Cannot find file '%2'\n") |
| .arg(fname, fileName); |
| m_errorDevice->write(msg.toUtf8()); |
| return false; |
| } |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| if (reader.hasError()) { |
| int errorLine = reader.lineNumber(); |
| int errorColumn = reader.columnNumber(); |
| QString errorMessage = reader.errorString(); |
| QString msg = QString::fromLatin1("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMessage); |
| m_errorDevice->write(msg.toUtf8()); |
| return false; |
| } |
| |
| if (m_root == 0) { |
| const QString msg = QString::fromLatin1("RCC: Warning: No resources in '%1'.\n").arg(fname); |
| m_errorDevice->write(msg.toUtf8()); |
| if (!listMode && m_format == Binary) { |
| // create dummy entry, otherwise loading with QResource will crash |
| m_root = new RCCFileInfo(QString(), QFileInfo(), |
| QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file) |
| { |
| Q_ASSERT(m_errorDevice); |
| if (file.m_fileInfo.size() > 0xffffffff) { |
| const QString msg = QString::fromLatin1("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath()); |
| m_errorDevice->write(msg.toUtf8()); |
| return false; |
| } |
| if (!m_root) |
| m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); |
| |
| RCCFileInfo *parent = m_root; |
| const QStringList nodes = alias.split(QLatin1Char('/')); |
| for (int i = 1; i < nodes.size()-1; ++i) { |
| const QString node = nodes.at(i); |
| if (node.isEmpty()) |
| continue; |
| if (!parent->m_children.contains(node)) { |
| RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); |
| s->m_parent = parent; |
| parent->m_children.insert(node, s); |
| parent = s; |
| } else { |
| parent = parent->m_children[node]; |
| } |
| } |
| |
| const QString filename = nodes.at(nodes.size()-1); |
| RCCFileInfo *s = new RCCFileInfo(file); |
| s->m_parent = parent; |
| typedef QHash<QString, RCCFileInfo*>::const_iterator ChildConstIterator; |
| const ChildConstIterator cbegin = parent->m_children.constFind(filename); |
| const ChildConstIterator cend = parent->m_children.constEnd(); |
| for (ChildConstIterator it = cbegin; it != cend; ++it) { |
| if (it.key() == filename && it.value()->m_language == s->m_language && |
| it.value()->m_country == s->m_country) { |
| for (const QString &name : qAsConst(m_fileNames)) { |
| qWarning("%s: Warning: potential duplicate alias detected: '%s'", |
| qPrintable(name), qPrintable(filename)); |
| } |
| break; |
| } |
| } |
| parent->m_children.insertMulti(filename, s); |
| return true; |
| } |
| |
| void RCCResourceLibrary::reset() |
| { |
| if (m_root) { |
| delete m_root; |
| m_root = 0; |
| } |
| m_errorDevice = 0; |
| m_failedResources.clear(); |
| } |
| |
| |
| bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice) |
| { |
| reset(); |
| m_errorDevice = &errorDevice; |
| //read in data |
| if (m_verbose) { |
| const QString msg = QString::fromLatin1("Processing %1 files [listMode=%2]\n") |
| .arg(m_fileNames.size()).arg(static_cast<int>(listMode)); |
| m_errorDevice->write(msg.toUtf8()); |
| } |
| for (int i = 0; i < m_fileNames.size(); ++i) { |
| QFile fileIn; |
| QString fname = m_fileNames.at(i); |
| QString pwd; |
| if (fname == QLatin1String("-")) { |
| fname = QLatin1String("(stdin)"); |
| pwd = QDir::currentPath(); |
| fileIn.setFileName(fname); |
| if (!fileIn.open(stdin, QIODevice::ReadOnly)) { |
| m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); |
| return false; |
| } |
| } else { |
| pwd = QFileInfo(fname).path(); |
| fileIn.setFileName(fname); |
| if (!fileIn.open(QIODevice::ReadOnly)) { |
| m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); |
| return false; |
| } |
| } |
| if (m_verbose) { |
| const QString msg = QString::fromLatin1("Interpreting %1\n").arg(fname); |
| m_errorDevice->write(msg.toUtf8()); |
| } |
| |
| if (!interpretResourceFile(&fileIn, fname, pwd, listMode)) |
| return false; |
| } |
| return true; |
| } |
| |
| QStringList RCCResourceLibrary::dataFiles() const |
| { |
| QStringList ret; |
| QStack<RCCFileInfo*> pending; |
| |
| if (!m_root) |
| return ret; |
| pending.push(m_root); |
| while (!pending.isEmpty()) { |
| RCCFileInfo *file = pending.pop(); |
| for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); |
| it != file->m_children.end(); ++it) { |
| RCCFileInfo *child = it.value(); |
| if (child->m_flags & RCCFileInfo::Directory) |
| pending.push(child); |
| else |
| ret.append(child->m_fileInfo.filePath()); |
| } |
| } |
| return ret; |
| } |
| |
| // Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion |
| static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) |
| { |
| typedef QHash<QString, RCCFileInfo*>::const_iterator ChildConstIterator; |
| const QChar slash = QLatin1Char('/'); |
| const ChildConstIterator cend = m_root->m_children.constEnd(); |
| for (ChildConstIterator it = m_root->m_children.constBegin(); it != cend; ++it) { |
| const RCCFileInfo *child = it.value(); |
| const QString childName = path + slash + child->m_name; |
| if (child->m_flags & RCCFileInfo::Directory) { |
| resourceDataFileMapRecursion(child, childName, m); |
| } else { |
| m.insert(childName, child->m_fileInfo.filePath()); |
| } |
| } |
| } |
| |
| RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const |
| { |
| ResourceDataFileMap rc; |
| if (m_root) |
| resourceDataFileMapRecursion(m_root, QString(QLatin1Char(':')), rc); |
| return rc; |
| } |
| |
| RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg) |
| { |
| if (value == QLatin1String("best")) |
| return CompressionAlgorithm::Best; |
| if (value == QLatin1String("zlib")) { |
| #ifdef QT_NO_COMPRESS |
| *errorMsg = QLatin1String("zlib support not compiled in"); |
| #else |
| return CompressionAlgorithm::Zlib; |
| #endif |
| } else if (value == QLatin1String("zstd")) { |
| #if QT_CONFIG(zstd) |
| return CompressionAlgorithm::Zstd; |
| #else |
| *errorMsg = QLatin1String("Zstandard support not compiled in"); |
| #endif |
| } else if (value != QLatin1String("none")) { |
| *errorMsg = QString::fromLatin1("Unknown compression algorithm '%1'").arg(value); |
| } |
| |
| return CompressionAlgorithm::None; |
| } |
| |
| int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg) |
| { |
| bool ok; |
| int c = level.toInt(&ok); |
| if (ok) { |
| switch (algo) { |
| case CompressionAlgorithm::None: |
| case CompressionAlgorithm::Best: |
| return 0; |
| case CompressionAlgorithm::Zlib: |
| if (c >= 1 && c <= 9) |
| return c; |
| break; |
| case CompressionAlgorithm::Zstd: |
| #if QT_CONFIG(zstd) |
| if (c >= 0 && c <= ZSTD_maxCLevel()) |
| return c; |
| #endif |
| break; |
| } |
| } |
| |
| *errorMsg = QString::fromLatin1("invalid compression level '%1'").arg(level); |
| return 0; |
| } |
| |
| bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice) |
| { |
| m_errorDevice = &errorDevice; |
| |
| if (m_format == Pass2) { |
| const char pattern[] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' }; |
| bool foundSignature = false; |
| |
| while (true) { |
| char c; |
| for (int i = 0; i < 8; ) { |
| if (!tempDevice.getChar(&c)) { |
| if (foundSignature) |
| return true; |
| m_errorDevice->write("No data signature found\n"); |
| return false; |
| } |
| if (c == pattern[i]) { |
| ++i; |
| } else { |
| for (int k = 0; k < i; ++k) |
| outDevice.putChar(pattern[k]); |
| outDevice.putChar(c); |
| i = 0; |
| } |
| } |
| |
| m_outDevice = &outDevice; |
| quint64 start = outDevice.pos(); |
| writeDataBlobs(); |
| quint64 len = outDevice.pos() - start; |
| |
| tempDevice.seek(tempDevice.pos() + len - 8); |
| foundSignature = true; |
| } |
| } |
| |
| //write out |
| if (m_verbose) |
| m_errorDevice->write("Outputting code\n"); |
| if (!writeHeader()) { |
| m_errorDevice->write("Could not write header\n"); |
| return false; |
| } |
| if (m_root) { |
| if (!writeDataBlobs()) { |
| m_errorDevice->write("Could not write data blobs.\n"); |
| return false; |
| } |
| if (!writeDataNames()) { |
| m_errorDevice->write("Could not write file names\n"); |
| return false; |
| } |
| if (!writeDataStructure()) { |
| m_errorDevice->write("Could not write data tree\n"); |
| return false; |
| } |
| } |
| if (!writeInitializer()) { |
| m_errorDevice->write("Could not write footer\n"); |
| return false; |
| } |
| outDevice.write(m_out.constData(), m_out.size()); |
| return true; |
| } |
| |
| void RCCResourceLibrary::writeDecimal(int value) |
| { |
| Q_ASSERT(m_format != RCCResourceLibrary::Binary); |
| char buf[std::numeric_limits<int>::digits10 + 2]; |
| int n = snprintf(buf, sizeof(buf), "%d", value); |
| write(buf, n); |
| } |
| |
| static const char hexDigits[] = "0123456789abcdef"; |
| |
| inline void RCCResourceLibrary::write2HexDigits(quint8 number) |
| { |
| writeChar(hexDigits[number >> 4]); |
| writeChar(hexDigits[number & 0xf]); |
| } |
| |
| void RCCResourceLibrary::writeHex(quint8 tmp) |
| { |
| switch (m_format) { |
| case RCCResourceLibrary::Python3_Code: |
| case RCCResourceLibrary::Python2_Code: |
| if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') { |
| writeChar(char(tmp)); |
| } else { |
| writeChar('\\'); |
| writeChar('x'); |
| write2HexDigits(tmp); |
| } |
| break; |
| default: |
| writeChar('0'); |
| writeChar('x'); |
| if (tmp < 16) |
| writeChar(hexDigits[tmp]); |
| else |
| write2HexDigits(tmp); |
| writeChar(','); |
| break; |
| } |
| } |
| |
| void RCCResourceLibrary::writeNumber2(quint16 number) |
| { |
| if (m_format == RCCResourceLibrary::Binary) { |
| writeChar(number >> 8); |
| writeChar(number); |
| } else { |
| writeHex(number >> 8); |
| writeHex(number); |
| } |
| } |
| |
| void RCCResourceLibrary::writeNumber4(quint32 number) |
| { |
| if (m_format == RCCResourceLibrary::Pass2) { |
| m_outDevice->putChar(char(number >> 24)); |
| m_outDevice->putChar(char(number >> 16)); |
| m_outDevice->putChar(char(number >> 8)); |
| m_outDevice->putChar(char(number)); |
| } else if (m_format == RCCResourceLibrary::Binary) { |
| writeChar(number >> 24); |
| writeChar(number >> 16); |
| writeChar(number >> 8); |
| writeChar(number); |
| } else { |
| writeHex(number >> 24); |
| writeHex(number >> 16); |
| writeHex(number >> 8); |
| writeHex(number); |
| } |
| } |
| |
| void RCCResourceLibrary::writeNumber8(quint64 number) |
| { |
| if (m_format == RCCResourceLibrary::Pass2) { |
| m_outDevice->putChar(char(number >> 56)); |
| m_outDevice->putChar(char(number >> 48)); |
| m_outDevice->putChar(char(number >> 40)); |
| m_outDevice->putChar(char(number >> 32)); |
| m_outDevice->putChar(char(number >> 24)); |
| m_outDevice->putChar(char(number >> 16)); |
| m_outDevice->putChar(char(number >> 8)); |
| m_outDevice->putChar(char(number)); |
| } else if (m_format == RCCResourceLibrary::Binary) { |
| writeChar(number >> 56); |
| writeChar(number >> 48); |
| writeChar(number >> 40); |
| writeChar(number >> 32); |
| writeChar(number >> 24); |
| writeChar(number >> 16); |
| writeChar(number >> 8); |
| writeChar(number); |
| } else { |
| writeHex(number >> 56); |
| writeHex(number >> 48); |
| writeHex(number >> 40); |
| writeHex(number >> 32); |
| writeHex(number >> 24); |
| writeHex(number >> 16); |
| writeHex(number >> 8); |
| writeHex(number); |
| } |
| } |
| |
| bool RCCResourceLibrary::writeHeader() |
| { |
| switch (m_format) { |
| case C_Code: |
| case Pass1: |
| writeString("/****************************************************************************\n"); |
| writeString("** Resource object code\n"); |
| 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"); |
| break; |
| case Python3_Code: |
| case Python2_Code: |
| writeString("# Resource object code (Python "); |
| writeChar(m_format == Python3_Code ? '3' : '2'); |
| writeString(")\n"); |
| writeString("# Created by: object code\n"); |
| writeString("# Created by: The Resource Compiler for Qt version "); |
| writeByteArray(QT_VERSION_STR); |
| writeString("\n"); |
| writeString("# WARNING! All changes made in this file will be lost!\n\n"); |
| writeString("from PySide2 import QtCore\n\n"); |
| break; |
| case Binary: |
| writeString("qres"); |
| writeNumber4(0); |
| writeNumber4(0); |
| writeNumber4(0); |
| writeNumber4(0); |
| if (m_formatVersion >= 3) |
| writeNumber4(m_overallFlags); |
| break; |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| bool RCCResourceLibrary::writeDataBlobs() |
| { |
| Q_ASSERT(m_errorDevice); |
| switch (m_format) { |
| case C_Code: |
| writeString("static const unsigned char qt_resource_data[] = {\n"); |
| break; |
| case Python3_Code: |
| writeString("qt_resource_data = b\"\\\n"); |
| break; |
| case Python2_Code: |
| writeString("qt_resource_data = \"\\\n"); |
| break; |
| case Binary: |
| m_dataOffset = m_out.size(); |
| break; |
| default: |
| break; |
| } |
| |
| if (!m_root) |
| return false; |
| |
| QStack<RCCFileInfo*> pending; |
| pending.push(m_root); |
| qint64 offset = 0; |
| QString errorMessage; |
| while (!pending.isEmpty()) { |
| RCCFileInfo *file = pending.pop(); |
| for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); |
| it != file->m_children.end(); ++it) { |
| RCCFileInfo *child = it.value(); |
| if (child->m_flags & RCCFileInfo::Directory) |
| pending.push(child); |
| else { |
| offset = child->writeDataBlob(*this, offset, &errorMessage); |
| if (offset == 0) { |
| m_errorDevice->write(errorMessage.toUtf8()); |
| return false; |
| } |
| } |
| } |
| } |
| switch (m_format) { |
| case C_Code: |
| writeString("\n};\n\n"); |
| break; |
| case Python3_Code: |
| case Python2_Code: |
| writeString("\"\n\n"); |
| break; |
| case Pass1: |
| if (offset < 8) |
| offset = 8; |
| writeString("\nstatic const unsigned char qt_resource_data["); |
| writeByteArray(QByteArray::number(offset)); |
| writeString("] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n"); |
| break; |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| bool RCCResourceLibrary::writeDataNames() |
| { |
| switch (m_format) { |
| case C_Code: |
| case Pass1: |
| writeString("static const unsigned char qt_resource_name[] = {\n"); |
| break; |
| case Python3_Code: |
| writeString("qt_resource_name = b\"\\\n"); |
| break; |
| case Python2_Code: |
| writeString("qt_resource_name = \"\\\n"); |
| break; |
| case Binary: |
| m_namesOffset = m_out.size(); |
| break; |
| default: |
| break; |
| } |
| |
| QHash<QString, int> names; |
| QStack<RCCFileInfo*> pending; |
| |
| if (!m_root) |
| return false; |
| |
| pending.push(m_root); |
| qint64 offset = 0; |
| while (!pending.isEmpty()) { |
| RCCFileInfo *file = pending.pop(); |
| for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); |
| it != file->m_children.end(); ++it) { |
| RCCFileInfo *child = it.value(); |
| if (child->m_flags & RCCFileInfo::Directory) |
| pending.push(child); |
| if (names.contains(child->m_name)) { |
| child->m_nameOffset = names.value(child->m_name); |
| } else { |
| names.insert(child->m_name, offset); |
| offset = child->writeDataName(*this, offset); |
| } |
| } |
| } |
| switch (m_format) { |
| case C_Code: |
| case Pass1: |
| writeString("\n};\n\n"); |
| break; |
| case Python3_Code: |
| case Python2_Code: |
| writeString("\"\n\n"); |
| break; |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| struct qt_rcc_compare_hash |
| { |
| typedef bool result_type; |
| result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const |
| { |
| return qt_hash(left->m_name) < qt_hash(right->m_name); |
| } |
| }; |
| |
| bool RCCResourceLibrary::writeDataStructure() |
| { |
| switch (m_format) { |
| case C_Code: |
| case Pass1: |
| writeString("static const unsigned char qt_resource_struct[] = {\n"); |
| break; |
| case Python3_Code: |
| writeString("qt_resource_struct = b\"\\\n"); |
| break; |
| case Python2_Code: |
| writeString("qt_resource_struct = \"\\\n"); |
| break; |
| case Binary: |
| m_treeOffset = m_out.size(); |
| break; |
| default: |
| break; |
| } |
| |
| QStack<RCCFileInfo*> pending; |
| |
| if (!m_root) |
| return false; |
| |
| //calculate the child offsets (flat) |
| pending.push(m_root); |
| int offset = 1; |
| while (!pending.isEmpty()) { |
| RCCFileInfo *file = pending.pop(); |
| file->m_childOffset = offset; |
| |
| //sort by hash value for binary lookup |
| QList<RCCFileInfo*> m_children = file->m_children.values(); |
| std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); |
| |
| //write out the actual data now |
| for (int i = 0; i < m_children.size(); ++i) { |
| RCCFileInfo *child = m_children.at(i); |
| ++offset; |
| if (child->m_flags & RCCFileInfo::Directory) |
| pending.push(child); |
| } |
| } |
| |
| //write out the structure (ie iterate again!) |
| pending.push(m_root); |
| m_root->writeDataInfo(*this); |
| while (!pending.isEmpty()) { |
| RCCFileInfo *file = pending.pop(); |
| |
| //sort by hash value for binary lookup |
| QList<RCCFileInfo*> m_children = file->m_children.values(); |
| std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); |
| |
| //write out the actual data now |
| for (int i = 0; i < m_children.size(); ++i) { |
| RCCFileInfo *child = m_children.at(i); |
| child->writeDataInfo(*this); |
| if (child->m_flags & RCCFileInfo::Directory) |
| pending.push(child); |
| } |
| } |
| switch (m_format) { |
| case C_Code: |
| case Pass1: |
| writeString("\n};\n\n"); |
| break; |
| case Python3_Code: |
| case Python2_Code: |
| writeString("\"\n\n"); |
| break; |
| default: |
| break; |
| } |
| |
| return true; |
| } |
| |
| void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name) |
| { |
| if (m_useNameSpace) { |
| writeString("QT_RCC_MANGLE_NAMESPACE("); |
| writeByteArray(name); |
| writeChar(')'); |
| } else { |
| writeByteArray(name); |
| } |
| } |
| |
| void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name) |
| { |
| if (m_useNameSpace) { |
| writeString("QT_RCC_PREPEND_NAMESPACE("); |
| writeByteArray(name); |
| writeChar(')'); |
| } else { |
| writeByteArray(name); |
| } |
| } |
| |
| bool RCCResourceLibrary::writeInitializer() |
| { |
| if (m_format == C_Code || m_format == Pass1) { |
| //write("\nQT_BEGIN_NAMESPACE\n"); |
| QString initNameStr = m_initName; |
| if (!initNameStr.isEmpty()) { |
| initNameStr.prepend(QLatin1Char('_')); |
| initNameStr.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), QLatin1String("_")); |
| } |
| QByteArray initName = initNameStr.toLatin1(); |
| |
| //init |
| if (m_useNameSpace) { |
| writeString("#ifdef QT_NAMESPACE\n" |
| "# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name\n" |
| "# define QT_RCC_MANGLE_NAMESPACE0(x) x\n" |
| "# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b\n" |
| "# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)\n" |
| "# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \\\n" |
| " QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))\n" |
| "#else\n" |
| "# define QT_RCC_PREPEND_NAMESPACE(name) name\n" |
| "# define QT_RCC_MANGLE_NAMESPACE(name) name\n" |
| "#endif\n\n"); |
| |
| writeString("#ifdef QT_NAMESPACE\n" |
| "namespace QT_NAMESPACE {\n" |
| "#endif\n\n"); |
| } |
| |
| if (m_root) { |
| writeString("bool qRegisterResourceData" |
| "(int, const unsigned char *, " |
| "const unsigned char *, const unsigned char *);\n"); |
| writeString("bool qUnregisterResourceData" |
| "(int, const unsigned char *, " |
| "const unsigned char *, const unsigned char *);\n\n"); |
| |
| if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) { |
| // use variable relocations with ELF and Mach-O |
| writeString("#if defined(__ELF__) || defined(__APPLE__)\n"); |
| if (m_overallFlags & RCCFileInfo::Compressed) { |
| writeString("static inline unsigned char qResourceFeatureZlib()\n" |
| "{\n" |
| " extern const unsigned char qt_resourceFeatureZlib;\n" |
| " return qt_resourceFeatureZlib;\n" |
| "}\n"); |
| } |
| if (m_overallFlags & RCCFileInfo::CompressedZstd) { |
| writeString("static inline unsigned char qResourceFeatureZstd()\n" |
| "{\n" |
| " extern const unsigned char qt_resourceFeatureZstd;\n" |
| " return qt_resourceFeatureZstd;\n" |
| "}\n"); |
| } |
| writeString("#else\n"); |
| if (m_overallFlags & RCCFileInfo::Compressed) |
| writeString("unsigned char qResourceFeatureZlib();\n"); |
| if (m_overallFlags & RCCFileInfo::CompressedZstd) |
| writeString("unsigned char qResourceFeatureZstd();\n"); |
| writeString("#endif\n\n"); |
| } |
| } |
| |
| if (m_useNameSpace) |
| writeString("#ifdef QT_NAMESPACE\n}\n#endif\n\n"); |
| |
| QByteArray initResources = "qInitResources"; |
| initResources += initName; |
| |
| // Work around -Wmissing-declarations warnings. |
| writeString("int "); |
| writeMangleNamespaceFunction(initResources); |
| writeString("();\n"); |
| |
| writeString("int "); |
| writeMangleNamespaceFunction(initResources); |
| writeString("()\n{\n"); |
| |
| if (m_root) { |
| writeString(" int version = "); |
| writeDecimal(m_formatVersion); |
| writeString(";\n "); |
| writeAddNamespaceFunction("qRegisterResourceData"); |
| writeString("\n (version, qt_resource_struct, " |
| "qt_resource_name, qt_resource_data);\n"); |
| } |
| writeString(" return 1;\n"); |
| writeString("}\n\n"); |
| |
| //cleanup |
| QByteArray cleanResources = "qCleanupResources"; |
| cleanResources += initName; |
| |
| // Work around -Wmissing-declarations warnings. |
| writeString("int "); |
| writeMangleNamespaceFunction(cleanResources); |
| writeString("();\n"); |
| |
| writeString("int "); |
| writeMangleNamespaceFunction(cleanResources); |
| writeString("()\n{\n"); |
| if (m_root) { |
| writeString(" int version = "); |
| writeDecimal(m_formatVersion); |
| writeString(";\n "); |
| |
| // ODR-use certain symbols from QtCore if we require optional features |
| if (m_overallFlags & RCCFileInfo::Compressed) { |
| writeString("version += "); |
| writeAddNamespaceFunction("qResourceFeatureZlib()"); |
| writeString(";\n "); |
| } |
| if (m_overallFlags & RCCFileInfo::CompressedZstd) { |
| writeString("version += "); |
| writeAddNamespaceFunction("qResourceFeatureZstd()"); |
| writeString(";\n "); |
| } |
| |
| writeAddNamespaceFunction("qUnregisterResourceData"); |
| writeString("\n (version, qt_resource_struct, " |
| "qt_resource_name, qt_resource_data);\n"); |
| } |
| writeString(" return 1;\n"); |
| writeString("}\n\n"); |
| |
| |
| writeString("namespace {\n" |
| " struct initializer {\n"); |
| |
| if (m_useNameSpace) { |
| writeByteArray(" initializer() { QT_RCC_MANGLE_NAMESPACE(" + initResources + ")(); }\n" |
| " ~initializer() { QT_RCC_MANGLE_NAMESPACE(" + cleanResources + ")(); }\n"); |
| } else { |
| writeByteArray(" initializer() { " + initResources + "(); }\n" |
| " ~initializer() { " + cleanResources + "(); }\n"); |
| } |
| writeString(" } dummy;\n" |
| "}\n"); |
| |
| } else if (m_format == Binary) { |
| int i = 4; |
| char *p = m_out.data(); |
| p[i++] = 0; |
| p[i++] = 0; |
| p[i++] = 0; |
| p[i++] = m_formatVersion; |
| |
| p[i++] = (m_treeOffset >> 24) & 0xff; |
| p[i++] = (m_treeOffset >> 16) & 0xff; |
| p[i++] = (m_treeOffset >> 8) & 0xff; |
| p[i++] = (m_treeOffset >> 0) & 0xff; |
| |
| p[i++] = (m_dataOffset >> 24) & 0xff; |
| p[i++] = (m_dataOffset >> 16) & 0xff; |
| p[i++] = (m_dataOffset >> 8) & 0xff; |
| p[i++] = (m_dataOffset >> 0) & 0xff; |
| |
| p[i++] = (m_namesOffset >> 24) & 0xff; |
| p[i++] = (m_namesOffset >> 16) & 0xff; |
| p[i++] = (m_namesOffset >> 8) & 0xff; |
| p[i++] = (m_namesOffset >> 0) & 0xff; |
| |
| if (m_formatVersion >= 3) { |
| p[i++] = (m_overallFlags >> 24) & 0xff; |
| p[i++] = (m_overallFlags >> 16) & 0xff; |
| p[i++] = (m_overallFlags >> 8) & 0xff; |
| p[i++] = (m_overallFlags >> 0) & 0xff; |
| } |
| } else if (m_format == Python3_Code || m_format == Python2_Code) { |
| writeString("def qInitResources():\n"); |
| writeString(" QtCore.qRegisterResourceData(0x"); |
| write2HexDigits(m_formatVersion); |
| writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); |
| writeString("def qCleanupResources():\n"); |
| writeString(" QtCore.qUnregisterResourceData(0x"); |
| write2HexDigits(m_formatVersion); |
| writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); |
| writeString("qInitResources()\n"); |
| } |
| return true; |
| } |
| |
| QT_END_NAMESPACE |