Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
rcc.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// Copyright (C) 2018 Intel Corporation.
3// Copyright (C) 2024 Christoph Cullmann <christoph@cullmann.io>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
5// Qt-Security score:significant reason:default
6
7#include "rcc.h"
8
9#include <qbytearray.h>
10#include <qcryptographichash.h>
11#include <qdatetime.h>
12#include <qdebug.h>
13#include <qdir.h>
14#include <qdirlisting.h>
15#include <qfile.h>
16#include <qiodevice.h>
17#include <qlocale.h>
18#include <qstack.h>
19#include <qxmlstream.h>
20
21#include <algorithm>
22
23#if QT_CONFIG(zstd)
24# include <zstd.h>
25#endif
26
27// Note: A copy of this file is used in Qt Widgets Designer (qttools/src/designer/src/lib/shared/rcc.cpp)
28
29QT_BEGIN_NAMESPACE
30
31using namespace Qt::StringLiterals;
32
33enum {
36 CONSTANT_ZSTDCOMPRESSLEVEL_CHECK = 1, // Zstd level to check if compressing is a good idea
37 CONSTANT_ZSTDCOMPRESSLEVEL_STORE = 14, // Zstd level to actually store the data
39};
40
41void RCCResourceLibrary::write(const char *str, int len)
42{
43 int n = m_out.size();
44 m_out.resize(n + len);
45 memcpy(m_out.data() + n, str, len);
46}
47
48void RCCResourceLibrary::writeByteArray(const QByteArray &other)
49{
50 if (m_format == Pass2) {
51 m_outDevice->write(other);
52 } else {
53 m_out.append(other);
54 }
55}
56
57static inline QString msgOpenReadFailed(const QString &fname, const QString &why)
58{
59 return QString::fromLatin1("Unable to open %1 for reading: %2\n").arg(fname, why);
60}
61
62
63///////////////////////////////////////////////////////////
64//
65// RCCFileInfo
66//
67///////////////////////////////////////////////////////////
68
70{
71public:
72 enum Flags
73 {
74 // must match qresource.cpp
75 NoFlags = 0x00,
76 Compressed = 0x01,
77 Directory = 0x02,
79 };
80
81
82 RCCFileInfo() = default;
83 RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language,
84 QLocale::Territory territory, uint flags,
85 RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel,
86 int compressThreshold, bool noZstd, bool isEmpty);
87
89 RCCFileInfo(const RCCFileInfo &) = delete;
90 RCCFileInfo &operator=(const RCCFileInfo &) = delete;
91 RCCFileInfo(RCCFileInfo &&) = default;
92 RCCFileInfo &operator=(RCCFileInfo &&other) = delete;
93
94 QString resourceName() const;
95
101
102 bool operator==(const DeduplicationKey &other) const
103 {
104 return compressAlgo == other.compressAlgo &&
105 compressLevel == other.compressLevel &&
106 compressThreshold == other.compressThreshold &&
107 hash == other.hash;
108 }
109 };
110
112
113public:
115 qint64 offset,
116 DeduplicationMultiHash &dedupByContent,
117 QString *errorMessage);
120
124 QString m_name;
125 QFileInfo m_fileInfo;
128
132 bool m_noZstd = false;
133 bool m_isEmpty = false;
134
138};
139
140static size_t qHash(const RCCFileInfo::DeduplicationKey &key, size_t seed) noexcept
141{
142 return qHashMulti(seed, key.compressAlgo, key.compressLevel, key.compressThreshold, key.hash);
143}
144
145RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language,
146 QLocale::Territory territory, uint flags,
147 RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel,
148 int compressThreshold, bool noZstd, bool isEmpty)
149 : m_flags(flags),
152 m_name(name),
154 m_compressAlgo(compressAlgo),
155 m_compressLevel(compressLevel),
156 m_compressThreshold(compressThreshold),
157 m_noZstd(noZstd),
158 m_isEmpty(isEmpty)
159{
160}
161
163{
164 qDeleteAll(m_children);
165}
166
167QString RCCFileInfo::resourceName() const
168{
169 QString resource = m_name;
170 for (RCCFileInfo *p = m_parent; p; p = p->m_parent)
171 resource = resource.prepend(p->m_name + u'/');
172 resource.prepend(u':');
173 return resource;
174}
175
177{
178 const bool text = lib.m_format == RCCResourceLibrary::C_Code;
179 const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
180 const bool python = lib.m_format == RCCResourceLibrary::Python_Code;
181 //some info
182 if (text || pass1) {
183 if (m_language != QLocale::C) {
184 lib.writeString(" // ");
185 lib.writeByteArray(resourceName().toLocal8Bit());
186 lib.writeString(" [");
187 lib.writeByteArray(QByteArray::number(m_territory));
188 lib.writeString("::");
189 lib.writeByteArray(QByteArray::number(m_language));
190 lib.writeString("[\n ");
191 } else {
192 lib.writeString(" // ");
193 lib.writeByteArray(resourceName().toLocal8Bit());
194 lib.writeString("\n ");
195 }
196 }
197
198 //pointer data
200 // name offset
201 lib.writeNumber4(m_nameOffset);
202
203 // flags
204 lib.writeNumber2(m_flags);
205
206 // child count
207 lib.writeNumber4(m_children.size());
208
209 // first child offset
210 lib.writeNumber4(m_childOffset);
211 } else {
212 // name offset
213 lib.writeNumber4(m_nameOffset);
214
215 // flags
216 lib.writeNumber2(m_flags);
217
218 // locale
219 lib.writeNumber2(m_territory);
220 lib.writeNumber2(m_language);
221
222 //data offset
223 lib.writeNumber4(m_dataOffset);
224 }
225 if (text || pass1)
226 lib.writeChar('\n');
227 else if (python)
228 lib.writeString("\\\n");
229
230 if (lib.formatVersion() >= 2) {
231 // last modified time stamp
232 const QDateTime lastModified = m_fileInfo.lastModified(QTimeZone::UTC);
233 quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0);
234 static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong();
235 if (sourceDate != 0)
236 lastmod = sourceDate;
237 static const quint64 sourceDate2 = 1000 * qgetenv("SOURCE_DATE_EPOCH").toULongLong();
238 if (sourceDate2 != 0)
239 lastmod = sourceDate2;
240 lib.writeNumber8(lastmod);
241 if (text || pass1)
242 lib.writeChar('\n');
243 else if (python)
244 lib.writeString("\\\n");
245 }
246}
247
249 qint64 offset,
250 DeduplicationMultiHash &dedupByContent,
251 QString *errorMessage)
252{
253 const bool text = lib.m_format == RCCResourceLibrary::C_Code;
254 const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
255 const bool pass2 = lib.m_format == RCCResourceLibrary::Pass2;
256 const bool binary = lib.m_format == RCCResourceLibrary::Binary;
257 const bool python = lib.m_format == RCCResourceLibrary::Python_Code;
258
259 //capture the offset
260 m_dataOffset = offset;
261 QByteArray data;
262
263 // determine compession algorithm & level early as used in de-duplication keys
264 // this avoid corruption for the two pass variants (QTBUG-137546)
265#if QT_CONFIG(zstd)
266 if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best && !m_noZstd) {
267 m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd;
268 m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental
269 }
270#endif
271#ifndef QT_NO_COMPRESS
274 m_compressLevel = 9;
275 }
276#endif
277
278 if (!m_isEmpty) {
279 // find the data to be written
280 const QString absoluteFilePath = m_fileInfo.absoluteFilePath();
281 QFile file(absoluteFilePath);
282 if (!file.open(QFile::ReadOnly)) {
283 *errorMessage = msgOpenReadFailed(absoluteFilePath, file.errorString());
284 return 0;
285 }
286 data = file.readAll();
287
288 // de-duplicate the same file content, we can re-use already written data
289 // we only do that if we have the same compression settings
290 const DeduplicationKey key{m_compressAlgo, m_compressLevel, m_compressThreshold,
291 QCryptographicHash::hash(data, QCryptographicHash::Sha256)};
292 const QList<RCCFileInfo *> potentialCandidates = dedupByContent.values(key);
293 for (const RCCFileInfo *candidate : potentialCandidates) {
294 // check real content, we can have collisions
295 QFile candidateFile(candidate->m_fileInfo.absoluteFilePath());
296 if (!candidateFile.open(QFile::ReadOnly)) {
297 *errorMessage = msgOpenReadFailed(candidate->m_fileInfo.absoluteFilePath(),
298 candidateFile.errorString());
299 return 0;
300 }
301 if (data != candidateFile.readAll())
302 continue;
303 // just remember the offset & flags with final compression state
304 // of the already written data and be done
305 m_dataOffset = candidate->m_dataOffset;
306 m_flags = candidate->m_flags;
307 return offset;
308 }
309 dedupByContent.insert(key, this);
310 }
311
312 // Check if compression is useful for this file
313 if (data.size() != 0) {
314#if QT_CONFIG(zstd)
315 if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd && !m_noZstd) {
316 qsizetype size = data.size();
317 size = ZSTD_COMPRESSBOUND(size);
318
319 int compressLevel = m_compressLevel;
320 if (compressLevel < 0)
321 compressLevel = CONSTANT_ZSTDCOMPRESSLEVEL_CHECK;
322
323 QByteArray compressed(size, Qt::Uninitialized);
324 char *dst = const_cast<char *>(compressed.constData());
325 size_t n = ZSTD_compress(dst, size,
326 data.constData(), data.size(),
327 compressLevel);
328 if (n * 100.0 < data.size() * 1.0 * (100 - m_compressThreshold) ) {
329 // compressing is worth it
330 if (m_compressLevel < 0) {
331 // heuristic compression, so recompress
332 n = ZSTD_compress(dst, size,
333 data.constData(), data.size(),
334 CONSTANT_ZSTDCOMPRESSLEVEL_STORE);
335 }
336 if (ZSTD_isError(n)) {
337 QString msg = "%1: error: compression with zstd failed: %2\n"_L1
338 .arg(m_name, ZSTD_getErrorName(n));
339 lib.m_errorDevice->write(msg.toUtf8());
340 } else if (lib.verbose()) {
341 QString msg = QString::fromLatin1("%1: note: compressed using zstd (%2 -> %3)\n")
342 .arg(m_name).arg(data.size()).arg(n);
343 lib.m_errorDevice->write(msg.toUtf8());
344 }
345
346 lib.m_overallFlags |= CompressedZstd;
347 m_flags |= CompressedZstd;
348 data = std::move(compressed);
349 data.truncate(n);
350 } else if (lib.verbose()) {
351 QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name);
352 lib.m_errorDevice->write(msg.toUtf8());
353 }
354 }
355#endif
356#ifndef QT_NO_COMPRESS
358 QByteArray compressed =
359 qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel);
360
361 int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size());
362 if (compressRatio >= m_compressThreshold) {
363 if (lib.verbose()) {
364 QString msg = QString::fromLatin1("%1: note: compressed using zlib (%2 -> %3)\n")
365 .arg(m_name).arg(data.size()).arg(compressed.size());
366 lib.m_errorDevice->write(msg.toUtf8());
367 }
368 data = std::move(compressed);
369 lib.m_overallFlags |= Compressed;
371 } else if (lib.verbose()) {
372 QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name);
373 lib.m_errorDevice->write(msg.toUtf8());
374 }
375 }
376#endif // QT_NO_COMPRESS
377 }
378
379 // some info
380 if (text || pass1) {
381 lib.writeString(" // ");
382 lib.writeByteArray(m_fileInfo.fileName().toLocal8Bit());
383 lib.writeString("\n ");
384 }
385
386 // write the length
387 if (text || binary || pass2 || python)
388 lib.writeNumber4(data.size());
389 if (text || pass1)
390 lib.writeString("\n ");
391 else if (python)
392 lib.writeString("\\\n");
393 offset += 4;
394
395 // write the payload
396 const char *p = data.constData();
397 if (text || python) {
398 for (int i = data.size(), j = 0; --i >= 0; --j) {
399 lib.writeHex(*p++);
400 if (j == 0) {
401 if (text)
402 lib.writeString("\n ");
403 else
404 lib.writeString("\\\n");
405 j = 16;
406 }
407 }
408 } else if (binary || pass2) {
409 lib.writeByteArray(data);
410 }
411 offset += data.size();
412
413 // done
414 if (text || pass1)
415 lib.writeString("\n ");
416 else if (python)
417 lib.writeString("\\\n");
418
419 return offset;
420}
421
423{
424 const bool text = lib.m_format == RCCResourceLibrary::C_Code;
425 const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
426 const bool python = lib.m_format == RCCResourceLibrary::Python_Code;
427
428 // capture the offset
429 m_nameOffset = offset;
430
431 // some info
432 if (text || pass1) {
433 lib.writeString(" // ");
434 lib.writeByteArray(m_name.toLocal8Bit());
435 lib.writeString("\n ");
436 }
437
438 // write the length
439 lib.writeNumber2(m_name.size());
440 if (text || pass1)
441 lib.writeString("\n ");
442 else if (python)
443 lib.writeString("\\\n");
444 offset += 2;
445
446 // write the hash
447 lib.writeNumber4(qt_hash(m_name));
448 if (text || pass1)
449 lib.writeString("\n ");
450 else if (python)
451 lib.writeString("\\\n");
452 offset += 4;
453
454 // write the m_name
455 const QChar *unicode = m_name.unicode();
456 for (int i = 0; i < m_name.size(); ++i) {
457 lib.writeNumber2(unicode[i].unicode());
458 if ((text || pass1) && i % 16 == 0)
459 lib.writeString("\n ");
460 else if (python && i % 16 == 0)
461 lib.writeString("\\\n");
462 }
463 offset += m_name.size()*2;
464
465 // done
466 if (text || pass1)
467 lib.writeString("\n ");
468 else if (python)
469 lib.writeString("\\\n");
470
471 return offset;
472}
473
474
475///////////////////////////////////////////////////////////
476//
477// RCCResourceLibrary
478//
479///////////////////////////////////////////////////////////
480
481RCCResourceLibrary::Strings::Strings() :
482 TAG_RCC("RCC"_L1),
483 TAG_RESOURCE("qresource"_L1),
484 TAG_FILE("file"_L1),
485 ATTRIBUTE_LANG("lang"_L1),
486 ATTRIBUTE_PREFIX("prefix"_L1),
487 ATTRIBUTE_ALIAS("alias"_L1),
488 ATTRIBUTE_EMPTY("empty"_L1),
489 ATTRIBUTE_THRESHOLD("threshold"_L1),
490 ATTRIBUTE_COMPRESS("compress"_L1),
491 ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm"))
492{
493}
494
496 : m_root(nullptr),
497 m_format(C_Code),
498 m_verbose(false),
499 m_compressionAlgo(CompressionAlgorithm::Best),
500 m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT),
501 m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT),
502 m_treeOffset(0),
503 m_namesOffset(0),
504 m_dataOffset(0),
506 m_useNameSpace(CONSTANT_USENAMESPACE),
507 m_errorDevice(nullptr),
508 m_outDevice(nullptr),
510 m_noZstd(false)
511{
512 m_out.reserve(30 * 1000 * 1000);
513}
514
516{
517 delete m_root;
518}
519
526
527static bool parseBoolean(QStringView value, QString *errorMsg)
528{
529 if (value.compare("true"_L1, Qt::CaseInsensitive) == 0)
530 return true;
531 if (value.compare("false"_L1, Qt::CaseInsensitive) == 0)
532 return false;
533
534 *errorMsg = QString::fromLatin1("Invalid value for boolean attribute: '%1'").arg(value);
535 return false;
536}
537
538bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
539 const QString &fname, QString currentPath, bool listMode)
540{
541 Q_ASSERT(m_errorDevice);
542 const QChar slash = u'/';
543 if (!currentPath.isEmpty() && !currentPath.endsWith(slash))
544 currentPath += slash;
545
546 QXmlStreamReader reader(inputDevice);
547 QStack<RCCXmlTag> tokens;
548
549 QString prefix;
550 QLocale::Language language = QLocale::c().language();
551 QLocale::Territory territory = QLocale::c().territory();
552 QString alias;
553 bool empty = false;
554 auto compressAlgo = m_compressionAlgo;
555 int compressLevel = m_compressLevel;
556 int compressThreshold = m_compressThreshold;
557
558 while (!reader.atEnd()) {
559 QXmlStreamReader::TokenType t = reader.readNext();
560 switch (t) {
561 case QXmlStreamReader::StartElement:
562 if (reader.name() == m_strings.TAG_RCC) {
563 if (!tokens.isEmpty())
564 reader.raiseError("expected <RCC> tag"_L1);
565 else
566 tokens.push(RccTag);
567 } else if (reader.name() == m_strings.TAG_LEGAL) {
568 if (tokens.isEmpty() || tokens.top() != RccTag) {
569 reader.raiseError("unexpected <legal> tag"_L1);
570 } else {
571 m_legal = reader.readElementText().trimmed();
572 }
573 } else if (reader.name() == m_strings.TAG_RESOURCE) {
574 if (tokens.isEmpty() || tokens.top() != RccTag) {
575 reader.raiseError("unexpected <RESOURCE> tag"_L1);
576 } else {
577 tokens.push(ResourceTag);
578
579 QXmlStreamAttributes attributes = reader.attributes();
580 language = QLocale::c().language();
581 territory = QLocale::c().territory();
582
583 if (attributes.hasAttribute(m_strings.ATTRIBUTE_LANG)) {
584 QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString();
585 QLocale lang = QLocale(attribute);
586 language = lang.language();
587 if (2 == attribute.size()) {
588 // Language only
589 territory = QLocale::AnyTerritory;
590 } else {
591 territory = lang.territory();
592 }
593 }
594
595 prefix.clear();
596 if (attributes.hasAttribute(m_strings.ATTRIBUTE_PREFIX))
597 prefix = attributes.value(m_strings.ATTRIBUTE_PREFIX).toString();
598 if (!prefix.startsWith(slash))
599 prefix.prepend(slash);
600 if (!prefix.endsWith(slash))
601 prefix += slash;
602 }
603 } else if (reader.name() == m_strings.TAG_FILE) {
604 if (tokens.isEmpty() || tokens.top() != ResourceTag) {
605 reader.raiseError("unexpected <FILE> tag"_L1);
606 } else {
607 tokens.push(FileTag);
608
609 QXmlStreamAttributes attributes = reader.attributes();
610 alias.clear();
611 if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS))
612 alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString();
613
614 compressAlgo = m_compressionAlgo;
615 compressLevel = m_compressLevel;
616 compressThreshold = m_compressThreshold;
617
618 QString errorString;
619 if (attributes.hasAttribute(m_strings.ATTRIBUTE_EMPTY))
620 empty = parseBoolean(attributes.value(m_strings.ATTRIBUTE_EMPTY), &errorString);
621 else
622 empty = false;
623
624 if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO))
625 compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString);
626 if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) {
627 QString value = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString();
628 compressLevel = parseCompressionLevel(compressAlgo, value, &errorString);
629 }
630
631 // Special case for -no-compress
632 if (m_compressLevel == -2)
633 compressAlgo = CompressionAlgorithm::None;
634
635 if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD))
636 compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt();
637
638 if (!errorString.isEmpty())
639 reader.raiseError(errorString);
640 }
641 } else {
642 reader.raiseError("unexpected tag: %1"_L1.arg(reader.name().toString()));
643 }
644 break;
645
646 case QXmlStreamReader::EndElement:
647 if (reader.name() == m_strings.TAG_RCC) {
648 if (!tokens.isEmpty() && tokens.top() == RccTag)
649 tokens.pop();
650 else
651 reader.raiseError("unexpected closing tag"_L1);
652 } else if (reader.name() == m_strings.TAG_RESOURCE) {
653 if (!tokens.isEmpty() && tokens.top() == ResourceTag)
654 tokens.pop();
655 else
656 reader.raiseError("unexpected closing tag"_L1);
657 } else if (reader.name() == m_strings.TAG_FILE) {
658 if (!tokens.isEmpty() && tokens.top() == FileTag)
659 tokens.pop();
660 else
661 reader.raiseError("unexpected closing tag"_L1);
662 }
663 break;
664
665 case QXmlStreamReader::Characters:
666 if (reader.isWhitespace())
667 break;
668 if (tokens.isEmpty() || tokens.top() != FileTag) {
669 reader.raiseError("unexpected text"_L1);
670 } else {
671 QString fileName = reader.text().toString();
672 if (fileName.isEmpty()) {
673 const QString msg = QString::fromLatin1("RCC: Warning: Null node in XML of '%1'\n").arg(fname);
674 m_errorDevice->write(msg.toUtf8());
675 }
676
677 if (alias.isNull())
678 alias = fileName;
679
680 alias = QDir::cleanPath(alias);
681 while (alias.startsWith("../"_L1))
682 alias.remove(0, 3);
683 alias = QDir::cleanPath(m_resourceRoot) + prefix + alias;
684
685 QString absFileName = fileName;
686 if (QDir::isRelativePath(absFileName))
687 absFileName.prepend(currentPath);
688 QFileInfo file(absFileName);
689 if (file.isDir()) {
690 if (!alias.endsWith(slash))
691 alias += slash;
692
693 QStringList filePaths;
694 using F = QDirListing::IteratorFlag;
695 constexpr auto flags = F::FollowDirSymlinks | F::Recursive;
696 for (const auto &entry : QDirListing(file.filePath(), flags)) {
697 const QString &fileName = entry.fileName();
698 if (fileName == "."_L1 || fileName == ".."_L1)
699 continue;
700 filePaths.emplace_back(entry.filePath());
701 }
702
703 // make rcc output deterministic
704 std::sort(filePaths.begin(), filePaths.end());
705
706 for (const QString &filePath : filePaths) {
707 QFileInfo child(filePath);
708 const bool arc =
709 addFile(alias + child.fileName(),
710 RCCFileInfo(child.fileName(), child, language, territory,
711 child.isDir() ? RCCFileInfo::Directory
712 : RCCFileInfo::NoFlags,
713 compressAlgo, compressLevel, compressThreshold,
714 m_noZstd, empty));
715 if (!arc)
716 m_failedResources.push_back(child.fileName());
717 }
718 } else if (listMode || file.isFile()) {
719 const bool arc =
720 addFile(alias,
721 RCCFileInfo(alias.section(slash, -1),
722 file,
723 language,
724 territory,
726 compressAlgo,
727 compressLevel,
728 compressThreshold,
729 m_noZstd, empty)
730 );
731 if (!arc)
732 m_failedResources.push_back(absFileName);
733 } else if (file.exists()) {
734 m_failedResources.push_back(absFileName);
735 const QString msg = QString::fromLatin1("RCC: Error in '%1': Entry '%2' is neither a file nor a directory\n")
736 .arg(fname, fileName);
737 m_errorDevice->write(msg.toUtf8());
738 return false;
739 } else {
740 m_failedResources.push_back(absFileName);
741 const QString msg = QString::fromLatin1("RCC: Error in '%1': Cannot find file '%2'\n")
742 .arg(fname, fileName);
743 m_errorDevice->write(msg.toUtf8());
744 return false;
745 }
746 }
747 break;
748
749 default:
750 break;
751 }
752 }
753
754 if (reader.hasError()) {
755 int errorLine = reader.lineNumber();
756 int errorColumn = reader.columnNumber();
757 QString errorMessage = reader.errorString();
758 QString msg = QString::fromLatin1("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMessage);
759 m_errorDevice->write(msg.toUtf8());
760 return false;
761 }
762
763 if (m_root == nullptr) {
764 const QString msg = QString::fromLatin1("RCC: Warning: No resources in '%1'.\n").arg(fname);
765 m_errorDevice->write(msg.toUtf8());
766 if (!listMode && m_format == Binary) {
767 // create dummy entry, otherwise loading with QResource will crash
768 m_root = new RCCFileInfo{};
770 }
771 }
772
773 return true;
774}
775
776bool RCCResourceLibrary::addFile(const QString &alias, RCCFileInfo file)
777{
778 Q_ASSERT(m_errorDevice);
779 if (file.m_fileInfo.size() > 0xffffffff) {
780 const QString msg = QString::fromLatin1("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath());
781 m_errorDevice->write(msg.toUtf8());
782 return false;
783 }
784 if (!m_root) {
785 m_root = new RCCFileInfo{};
787 }
788
789 RCCFileInfo *parent = m_root;
790 const QStringList nodes = alias.split(u'/');
791 for (int i = 1; i < nodes.size()-1; ++i) {
792 const QString node = nodes.at(i);
793 if (node.isEmpty())
794 continue;
795 if (!parent->m_children.contains(node)) {
796 RCCFileInfo *s = new RCCFileInfo{};
797 s->m_name = node;
799 s->m_parent = parent;
800 parent->m_children.insert(node, s);
801 parent = s;
802 } else {
803 parent = *parent->m_children.constFind(node);
804 }
805 }
806
807 const QString filename = nodes.at(nodes.size()-1);
808 RCCFileInfo *s = new RCCFileInfo(std::move(file));
809 s->m_parent = parent;
810 auto cbegin = parent->m_children.constFind(filename);
811 auto cend = parent->m_children.constEnd();
812 for (auto it = cbegin; it != cend; ++it) {
813 if (it.key() == filename && it.value()->m_language == s->m_language &&
814 it.value()->m_territory == s->m_territory) {
815 for (const QString &name : std::as_const(m_fileNames)) {
816 qWarning("%s: Warning: potential duplicate alias detected: '%s'",
817 qPrintable(name), qPrintable(filename));
818 }
819 break;
820 }
821 }
822 parent->m_children.insert(filename, s);
823 return true;
824}
825
826void RCCResourceLibrary::reset()
827{
828 if (m_root) {
829 delete m_root;
830 m_root = nullptr;
831 }
832 m_errorDevice = nullptr;
833 m_failedResources.clear();
834}
835
836
837bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice)
838{
839 reset();
840 m_errorDevice = &errorDevice;
841 //read in data
842 if (m_verbose) {
843 const QString msg = QString::fromLatin1("Processing %1 files [listMode=%2]\n")
844 .arg(m_fileNames.size()).arg(static_cast<int>(listMode));
845 m_errorDevice->write(msg.toUtf8());
846 }
847 for (int i = 0; i < m_fileNames.size(); ++i) {
848 QFile fileIn;
849 QString fname = m_fileNames.at(i);
850 QString pwd;
851 if (fname == "-"_L1) {
852 fname = "(stdin)"_L1;
853 pwd = QDir::currentPath();
854 fileIn.setFileName(fname);
855 if (!fileIn.open(stdin, QIODevice::ReadOnly)) {
856 m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8());
857 return false;
858 }
859 } else {
860 pwd = QFileInfo(fname).path();
861 fileIn.setFileName(fname);
862 if (!fileIn.open(QIODevice::ReadOnly)) {
863 m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8());
864 return false;
865 }
866 }
867 if (m_verbose) {
868 const QString msg = QString::fromLatin1("Interpreting %1\n").arg(fname);
869 m_errorDevice->write(msg.toUtf8());
870 }
871
872 if (!interpretResourceFile(&fileIn, fname, std::move(pwd), listMode))
873 return false;
874 }
875 return true;
876}
877
879{
880 QStringList ret;
881 QStack<RCCFileInfo*> pending;
882
883 if (!m_root)
884 return ret;
885 pending.push(m_root);
886 while (!pending.isEmpty()) {
887 RCCFileInfo *file = pending.pop();
888 for (auto it = file->m_children.begin();
889 it != file->m_children.end(); ++it) {
890 RCCFileInfo *child = it.value();
892 pending.push(child);
893 else
894 ret.append(child->m_fileInfo.filePath());
895 }
896 }
897 return ret;
898}
899
900// Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion
901static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m)
902{
903 const QChar slash = u'/';
904 const auto cend = m_root->m_children.constEnd();
905 for (auto it = m_root->m_children.constBegin(); it != cend; ++it) {
906 const RCCFileInfo *child = it.value();
907 const QString childName = path + slash + child->m_name;
909 resourceDataFileMapRecursion(child, childName, m);
910 } else {
911 m.insert(childName, child->m_fileInfo.filePath());
912 }
913 }
914}
915
917{
919 if (m_root)
920 resourceDataFileMapRecursion(m_root, QString(u':'), rc);
921 return rc;
922}
923
924RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg)
925{
926 if (value == "best"_L1)
928 if (value == "zlib"_L1) {
929#ifdef QT_NO_COMPRESS
930 *errorMsg = "zlib support not compiled in"_L1;
931#else
933#endif
934 } else if (value == "zstd"_L1) {
935#if QT_CONFIG(zstd)
936 return CompressionAlgorithm::Zstd;
937#else
938 *errorMsg = "Zstandard support not compiled in"_L1;
939#endif
940 } else if (value != "none"_L1) {
941 *errorMsg = QString::fromLatin1("Unknown compression algorithm '%1'").arg(value);
942 }
943
945}
946
947int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg)
948{
949 bool ok;
950 int c = level.toInt(&ok);
951 if (ok) {
952 switch (algo) {
955 return 0;
957 if (c >= 1 && c <= 9)
958 return c;
959 break;
961#if QT_CONFIG(zstd)
962 if (c >= 0 && c <= ZSTD_maxCLevel())
963 return c;
964#endif
965 break;
966 }
967 }
968
969 *errorMsg = QString::fromLatin1("invalid compression level '%1'").arg(level);
970 return 0;
971}
972
973bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice)
974{
975 m_errorDevice = &errorDevice;
976
977 if (m_format == Pass2) {
978 const char pattern[] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };
979 bool foundSignature = false;
980
981 while (true) {
982 char c;
983 for (int i = 0; i < 8; ) {
984 if (!tempDevice.getChar(&c)) {
985 if (foundSignature)
986 return true;
987 m_errorDevice->write("No data signature found\n");
988 return false;
989 }
990
991 if (c != pattern[i]) {
992 for (int k = 0; k < i; ++k)
993 outDevice.putChar(pattern[k]);
994 i = 0;
995 }
996
997 if (c == pattern[i]) {
998 ++i;
999 } else {
1000 outDevice.putChar(c);
1001 }
1002 }
1003
1004 m_outDevice = &outDevice;
1005 quint64 start = outDevice.pos();
1006 writeDataBlobs();
1007 quint64 len = outDevice.pos() - start;
1008
1009 tempDevice.seek(tempDevice.pos() + len - 8);
1010 foundSignature = true;
1011 }
1012 }
1013
1014 //write out
1015 if (m_verbose)
1016 m_errorDevice->write("Outputting code\n");
1017 if (!writeHeader()) {
1018 m_errorDevice->write("Could not write header\n");
1019 return false;
1020 }
1021 if (m_root) {
1022 if (!writeDataBlobs()) {
1023 m_errorDevice->write("Could not write data blobs.\n");
1024 return false;
1025 }
1026 if (!writeDataNames()) {
1027 m_errorDevice->write("Could not write file names\n");
1028 return false;
1029 }
1030 if (!writeDataStructure()) {
1031 m_errorDevice->write("Could not write data tree\n");
1032 return false;
1033 }
1034 }
1035 if (!writeInitializer()) {
1036 m_errorDevice->write("Could not write footer\n");
1037 return false;
1038 }
1039 outDevice.write(m_out.constData(), m_out.size());
1040 return true;
1041}
1042
1043void RCCResourceLibrary::writeDecimal(int value)
1044{
1045 Q_ASSERT(m_format != RCCResourceLibrary::Binary);
1046 char buf[std::numeric_limits<int>::digits10 + 2];
1047 int n = snprintf(buf, sizeof(buf), "%d", value);
1048 write(buf, n);
1049}
1050
1051static const char hexDigits[] = "0123456789abcdef";
1052
1053inline void RCCResourceLibrary::write2HexDigits(quint8 number)
1054{
1055 writeChar(hexDigits[number >> 4]);
1056 writeChar(hexDigits[number & 0xf]);
1057}
1058
1059void RCCResourceLibrary::writeHex(quint8 tmp)
1060{
1061 switch (m_format) {
1063 if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') {
1064 writeChar(char(tmp));
1065 } else {
1066 writeChar('\\');
1067 writeChar('x');
1068 write2HexDigits(tmp);
1069 }
1070 break;
1071 default:
1072 writeChar('0');
1073 writeChar('x');
1074 if (tmp < 16)
1075 writeChar(hexDigits[tmp]);
1076 else
1077 write2HexDigits(tmp);
1078 writeChar(',');
1079 break;
1080 }
1081}
1082
1083void RCCResourceLibrary::writeNumber2(quint16 number)
1084{
1085 if (m_format == RCCResourceLibrary::Binary) {
1086 writeChar(number >> 8);
1087 writeChar(number);
1088 } else {
1089 writeHex(number >> 8);
1090 writeHex(number);
1091 }
1092}
1093
1094void RCCResourceLibrary::writeNumber4(quint32 number)
1095{
1096 if (m_format == RCCResourceLibrary::Pass2) {
1097 m_outDevice->putChar(char(number >> 24));
1098 m_outDevice->putChar(char(number >> 16));
1099 m_outDevice->putChar(char(number >> 8));
1100 m_outDevice->putChar(char(number));
1101 } else if (m_format == RCCResourceLibrary::Binary) {
1102 writeChar(number >> 24);
1103 writeChar(number >> 16);
1104 writeChar(number >> 8);
1105 writeChar(number);
1106 } else {
1107 writeHex(number >> 24);
1108 writeHex(number >> 16);
1109 writeHex(number >> 8);
1110 writeHex(number);
1111 }
1112}
1113
1114void RCCResourceLibrary::writeNumber8(quint64 number)
1115{
1116 if (m_format == RCCResourceLibrary::Pass2) {
1117 m_outDevice->putChar(char(number >> 56));
1118 m_outDevice->putChar(char(number >> 48));
1119 m_outDevice->putChar(char(number >> 40));
1120 m_outDevice->putChar(char(number >> 32));
1121 m_outDevice->putChar(char(number >> 24));
1122 m_outDevice->putChar(char(number >> 16));
1123 m_outDevice->putChar(char(number >> 8));
1124 m_outDevice->putChar(char(number));
1125 } else if (m_format == RCCResourceLibrary::Binary) {
1126 writeChar(number >> 56);
1127 writeChar(number >> 48);
1128 writeChar(number >> 40);
1129 writeChar(number >> 32);
1130 writeChar(number >> 24);
1131 writeChar(number >> 16);
1132 writeChar(number >> 8);
1133 writeChar(number);
1134 } else {
1135 writeHex(number >> 56);
1136 writeHex(number >> 48);
1137 writeHex(number >> 40);
1138 writeHex(number >> 32);
1139 writeHex(number >> 24);
1140 writeHex(number >> 16);
1141 writeHex(number >> 8);
1142 writeHex(number);
1143 }
1144}
1145
1146bool RCCResourceLibrary::writeHeader()
1147{
1148 auto writeCopyright = [this](QByteArrayView prefix) {
1149 const QStringList lines = m_legal.split(u'\n', Qt::SkipEmptyParts);
1150 for (const QString &line : lines) {
1151 write(prefix.data(), prefix.size());
1152 writeString(line.toUtf8().trimmed());
1153 writeChar('\n');
1154 }
1155 };
1156 switch (m_format) {
1157 case C_Code:
1158 case Pass1:
1159 writeString("/****************************************************************************\n");
1160 writeString("** Resource object code\n");
1161 writeCopyright("** ");
1162 writeString("**\n");
1163 writeString("** Created by: The Resource Compiler for Qt version ");
1164 writeByteArray(QT_VERSION_STR);
1165 writeString("\n**\n");
1166 writeString("** WARNING! All changes made in this file will be lost!\n");
1167 writeString( "*****************************************************************************/\n\n");
1168 writeString("#ifdef _MSC_VER\n"
1169 "// disable informational message \"function ... selected for automatic inline expansion\"\n"
1170 "#pragma warning (disable: 4711)\n"
1171 "#endif\n\n");
1172 break;
1173 case Python_Code:
1174 writeString("# Resource object code (Python 3)\n");
1175 writeCopyright("# ");
1176 writeString("# Created by: object code\n");
1177 writeString("# Created by: The Resource Compiler for Qt version ");
1178 writeByteArray(QT_VERSION_STR);
1179 writeString("\n");
1180 writeString("# WARNING! All changes made in this file will be lost!\n\n");
1181 writeString("from PySide");
1182 writeByteArray(QByteArray::number(QT_VERSION_MAJOR));
1183 writeString(" import QtCore\n\n");
1184 break;
1185 case Binary:
1186 writeString("qres");
1187 writeNumber4(0);
1188 writeNumber4(0);
1189 writeNumber4(0);
1190 writeNumber4(0);
1191 if (m_formatVersion >= 3)
1192 writeNumber4(m_overallFlags);
1193 break;
1194 default:
1195 break;
1196 }
1197 return true;
1198}
1199
1200bool RCCResourceLibrary::writeDataBlobs()
1201{
1202 Q_ASSERT(m_errorDevice);
1203 switch (m_format) {
1204 case C_Code:
1205 writeString("static const unsigned char qt_resource_data[] = {\n");
1206 break;
1207 case Python_Code:
1208 writeString("qt_resource_data = b\"\\\n");
1209 break;
1210 case Binary:
1211 m_dataOffset = m_out.size();
1212 break;
1213 default:
1214 break;
1215 }
1216
1217 if (!m_root)
1218 return false;
1219
1220 QStack<RCCFileInfo*> pending;
1221 pending.push(m_root);
1222 qint64 offset = 0;
1223 RCCFileInfo::DeduplicationMultiHash dedupByContent;
1224 QString errorMessage;
1225 while (!pending.isEmpty()) {
1226 RCCFileInfo *file = pending.pop();
1227 for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) {
1228 RCCFileInfo *child = it.value();
1230 pending.push(child);
1231 else {
1232 offset = child->writeDataBlob(*this, offset,
1233 dedupByContent, &errorMessage);
1234 if (offset == 0) {
1235 m_errorDevice->write(errorMessage.toUtf8());
1236 return false;
1237 }
1238 }
1239 }
1240 }
1241 switch (m_format) {
1242 case C_Code:
1243 writeString("\n};\n\n");
1244 break;
1245 case Python_Code:
1246 writeString("\"\n\n");
1247 break;
1248 case Pass1:
1249 if (offset < 8)
1250 offset = 8;
1251 writeString("\nstatic const unsigned char qt_resource_data[");
1252 writeByteArray(QByteArray::number(offset));
1253 writeString("] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n");
1254 break;
1255 default:
1256 break;
1257 }
1258 return true;
1259}
1260
1261bool RCCResourceLibrary::writeDataNames()
1262{
1263 switch (m_format) {
1264 case C_Code:
1265 case Pass1:
1266 writeString("static const unsigned char qt_resource_name[] = {\n");
1267 break;
1268 case Python_Code:
1269 writeString("qt_resource_name = b\"\\\n");
1270 break;
1271 case Binary:
1272 m_namesOffset = m_out.size();
1273 break;
1274 default:
1275 break;
1276 }
1277
1278 QHash<QString, int> names;
1279 QStack<RCCFileInfo*> pending;
1280
1281 if (!m_root)
1282 return false;
1283
1284 pending.push(m_root);
1285 qint64 offset = 0;
1286 while (!pending.isEmpty()) {
1287 RCCFileInfo *file = pending.pop();
1288 for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) {
1289 RCCFileInfo *child = it.value();
1291 pending.push(child);
1292 if (names.contains(child->m_name)) {
1293 child->m_nameOffset = names.value(child->m_name);
1294 } else {
1295 names.insert(child->m_name, offset);
1296 offset = child->writeDataName(*this, offset);
1297 }
1298 }
1299 }
1300 switch (m_format) {
1301 case C_Code:
1302 case Pass1:
1303 writeString("\n};\n\n");
1304 break;
1305 case Python_Code:
1306 writeString("\"\n\n");
1307 break;
1308 default:
1309 break;
1310 }
1311 return true;
1312}
1313
1315{
1316 typedef bool result_type;
1317 result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const
1318 {
1319 return qt_hash(left->m_name) < qt_hash(right->m_name);
1320 }
1321};
1322
1323bool RCCResourceLibrary::writeDataStructure()
1324{
1325 switch (m_format) {
1326 case C_Code:
1327 case Pass1:
1328 writeString("static const unsigned char qt_resource_struct[] = {\n");
1329 break;
1330 case Python_Code:
1331 writeString("qt_resource_struct = b\"\\\n");
1332 break;
1333 case Binary:
1334 m_treeOffset = m_out.size();
1335 break;
1336 default:
1337 break;
1338 }
1339
1340 QStack<RCCFileInfo*> pending;
1341
1342 if (!m_root)
1343 return false;
1344
1345 //calculate the child offsets (flat)
1346 pending.push(m_root);
1347 int offset = 1;
1348 while (!pending.isEmpty()) {
1349 RCCFileInfo *file = pending.pop();
1350 file->m_childOffset = offset;
1351
1352 //sort by hash value for binary lookup
1353 QList<RCCFileInfo*> m_children = file->m_children.values();
1354 std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash());
1355
1356 //write out the actual data now
1357 for (int i = 0; i < m_children.size(); ++i) {
1358 RCCFileInfo *child = m_children.at(i);
1359 ++offset;
1361 pending.push(child);
1362 }
1363 }
1364
1365 //write out the structure (ie iterate again!)
1366 pending.push(m_root);
1367 m_root->writeDataInfo(*this);
1368 while (!pending.isEmpty()) {
1369 RCCFileInfo *file = pending.pop();
1370
1371 //sort by hash value for binary lookup
1372 QList<RCCFileInfo*> m_children = file->m_children.values();
1373 std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash());
1374
1375 //write out the actual data now
1376 for (int i = 0; i < m_children.size(); ++i) {
1377 RCCFileInfo *child = m_children.at(i);
1378 child->writeDataInfo(*this);
1380 pending.push(child);
1381 }
1382 }
1383 switch (m_format) {
1384 case C_Code:
1385 case Pass1:
1386 writeString("\n};\n\n");
1387 break;
1388 case Python_Code:
1389 writeString("\"\n\n");
1390 break;
1391 default:
1392 break;
1393 }
1394
1395 return true;
1396}
1397
1398void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name)
1399{
1400 if (m_useNameSpace) {
1401 writeString("QT_RCC_MANGLE_NAMESPACE(");
1402 writeByteArray(name);
1403 writeChar(')');
1404 } else {
1405 writeByteArray(name);
1406 }
1407}
1408
1409void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name)
1410{
1411 if (m_useNameSpace) {
1412 writeString("QT_RCC_PREPEND_NAMESPACE(");
1413 writeByteArray(name);
1414 writeChar(')');
1415 } else {
1416 writeByteArray(name);
1417 }
1418}
1419
1420bool RCCResourceLibrary::writeInitializer()
1421{
1422 if (m_format == C_Code || m_format == Pass1) {
1423 //write("\nQT_BEGIN_NAMESPACE\n");
1424 QString initNameStr = m_initName;
1425 if (!initNameStr.isEmpty()) {
1426 initNameStr.prepend(u'_');
1427 auto isAsciiLetterOrNumber = [] (QChar c) -> bool {
1428 ushort ch = c.unicode();
1429 return (ch >= '0' && ch <= '9') ||
1430 (ch >= 'A' && ch <= 'Z') ||
1431 (ch >= 'a' && ch <= 'z') ||
1432 ch == '_';
1433 };
1434 for (QChar &c : initNameStr) {
1435 if (!isAsciiLetterOrNumber(c))
1436 c = u'_';
1437 }
1438 }
1439 QByteArray initName = initNameStr.toLatin1();
1440
1441 //init
1442 if (m_useNameSpace) {
1443 writeString("#ifdef QT_NAMESPACE\n"
1444 "# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name\n"
1445 "# define QT_RCC_MANGLE_NAMESPACE0(x) x\n"
1446 "# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b\n"
1447 "# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)\n"
1448 "# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \\\n"
1449 " QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))\n"
1450 "#else\n"
1451 "# define QT_RCC_PREPEND_NAMESPACE(name) name\n"
1452 "# define QT_RCC_MANGLE_NAMESPACE(name) name\n"
1453 "#endif\n\n");
1454
1455 writeString("#if defined(QT_INLINE_NAMESPACE)\n"
1456 "inline namespace QT_NAMESPACE {\n"
1457 "#elif defined(QT_NAMESPACE)\n"
1458 "namespace QT_NAMESPACE {\n"
1459 "#endif\n\n");
1460 }
1461
1462 if (m_root) {
1463 writeString("bool qRegisterResourceData"
1464 "(int, const unsigned char *, "
1465 "const unsigned char *, const unsigned char *);\n");
1466 writeString("bool qUnregisterResourceData"
1467 "(int, const unsigned char *, "
1468 "const unsigned char *, const unsigned char *);\n\n");
1469
1470 if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) {
1471 // use variable relocations with ELF and Mach-O
1472 writeString("#if defined(__ELF__) || defined(__APPLE__)\n");
1473 if (m_overallFlags & RCCFileInfo::Compressed) {
1474 writeString("static inline unsigned char qResourceFeatureZlib()\n"
1475 "{\n"
1476 " extern const unsigned char qt_resourceFeatureZlib;\n"
1477 " return qt_resourceFeatureZlib;\n"
1478 "}\n");
1479 }
1480 if (m_overallFlags & RCCFileInfo::CompressedZstd) {
1481 writeString("static inline unsigned char qResourceFeatureZstd()\n"
1482 "{\n"
1483 " extern const unsigned char qt_resourceFeatureZstd;\n"
1484 " return qt_resourceFeatureZstd;\n"
1485 "}\n");
1486 }
1487 writeString("#else\n");
1488 if (m_overallFlags & RCCFileInfo::Compressed)
1489 writeString("unsigned char qResourceFeatureZlib();\n");
1490 if (m_overallFlags & RCCFileInfo::CompressedZstd)
1491 writeString("unsigned char qResourceFeatureZstd();\n");
1492 writeString("#endif\n\n");
1493 }
1494 }
1495
1496 if (m_useNameSpace)
1497 writeString("#ifdef QT_NAMESPACE\n}\n#endif\n\n");
1498
1499 QByteArray initResources = "qInitResources";
1500 initResources += initName;
1501
1502 // Work around -Wmissing-declarations warnings.
1503 writeString("int ");
1504 writeMangleNamespaceFunction(initResources);
1505 writeString("();\n");
1506
1507 writeString("int ");
1508 writeMangleNamespaceFunction(initResources);
1509 writeString("()\n{\n");
1510
1511 if (m_root) {
1512 writeString(" int version = ");
1513 writeDecimal(m_formatVersion);
1514 writeString(";\n ");
1515 writeAddNamespaceFunction("qRegisterResourceData");
1516 writeString("\n (version, qt_resource_struct, "
1517 "qt_resource_name, qt_resource_data);\n");
1518 }
1519 writeString(" return 1;\n");
1520 writeString("}\n\n");
1521
1522 //cleanup
1523 QByteArray cleanResources = "qCleanupResources";
1524 cleanResources += initName;
1525
1526 // Work around -Wmissing-declarations warnings.
1527 writeString("int ");
1528 writeMangleNamespaceFunction(cleanResources);
1529 writeString("();\n");
1530
1531 writeString("int ");
1532 writeMangleNamespaceFunction(cleanResources);
1533 writeString("()\n{\n");
1534 if (m_root) {
1535 writeString(" int version = ");
1536 writeDecimal(m_formatVersion);
1537 writeString(";\n ");
1538
1539 // ODR-use certain symbols from QtCore if we require optional features
1540 if (m_overallFlags & RCCFileInfo::Compressed) {
1541 writeString("version += ");
1542 writeAddNamespaceFunction("qResourceFeatureZlib()");
1543 writeString(";\n ");
1544 }
1545 if (m_overallFlags & RCCFileInfo::CompressedZstd) {
1546 writeString("version += ");
1547 writeAddNamespaceFunction("qResourceFeatureZstd()");
1548 writeString(";\n ");
1549 }
1550
1551 writeAddNamespaceFunction("qUnregisterResourceData");
1552 writeString("\n (version, qt_resource_struct, "
1553 "qt_resource_name, qt_resource_data);\n");
1554 }
1555 writeString(" return 1;\n");
1556 writeString("}\n\n");
1557
1558 // -Wexit-time-destructors was added to clang 3.0.0 in 2011.
1559 writeString("#ifdef __clang__\n"
1560 "# pragma clang diagnostic push\n"
1561 "# pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n"
1562 "#endif\n\n");
1563
1564 writeString("namespace {\n"
1565 " struct initializer {\n");
1566
1567 if (m_useNameSpace) {
1568 writeByteArray(" initializer() { QT_RCC_MANGLE_NAMESPACE(" + initResources + ")(); }\n"
1569 " ~initializer() { QT_RCC_MANGLE_NAMESPACE(" + cleanResources + ")(); }\n");
1570 } else {
1571 writeByteArray(" initializer() { " + initResources + "(); }\n"
1572 " ~initializer() { " + cleanResources + "(); }\n");
1573 }
1574 writeString(" } dummy;\n"
1575 "}\n\n");
1576
1577 writeString("#ifdef __clang__\n"
1578 "# pragma clang diagnostic pop\n"
1579 "#endif\n");
1580
1581
1582 } else if (m_format == Binary) {
1583 int i = 4;
1584 char *p = m_out.data();
1585 p[i++] = 0;
1586 p[i++] = 0;
1587 p[i++] = 0;
1588 p[i++] = m_formatVersion;
1589
1590 p[i++] = (m_treeOffset >> 24) & 0xff;
1591 p[i++] = (m_treeOffset >> 16) & 0xff;
1592 p[i++] = (m_treeOffset >> 8) & 0xff;
1593 p[i++] = (m_treeOffset >> 0) & 0xff;
1594
1595 p[i++] = (m_dataOffset >> 24) & 0xff;
1596 p[i++] = (m_dataOffset >> 16) & 0xff;
1597 p[i++] = (m_dataOffset >> 8) & 0xff;
1598 p[i++] = (m_dataOffset >> 0) & 0xff;
1599
1600 p[i++] = (m_namesOffset >> 24) & 0xff;
1601 p[i++] = (m_namesOffset >> 16) & 0xff;
1602 p[i++] = (m_namesOffset >> 8) & 0xff;
1603 p[i++] = (m_namesOffset >> 0) & 0xff;
1604
1605 if (m_formatVersion >= 3) {
1606 p[i++] = (m_overallFlags >> 24) & 0xff;
1607 p[i++] = (m_overallFlags >> 16) & 0xff;
1608 p[i++] = (m_overallFlags >> 8) & 0xff;
1609 p[i++] = (m_overallFlags >> 0) & 0xff;
1610 }
1611 } else if (m_format == Python_Code) {
1612 writeString("def qInitResources():\n");
1613 writeString(" QtCore.qRegisterResourceData(0x");
1614 write2HexDigits(m_formatVersion);
1615 writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n");
1616 writeString("def qCleanupResources():\n");
1617 writeString(" QtCore.qUnregisterResourceData(0x");
1618 write2HexDigits(m_formatVersion);
1619 writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n");
1620 writeString("qInitResources()\n");
1621 }
1622 return true;
1623}
1624
1625QT_END_NAMESPACE
Definition qfile.h:71
\inmodule QtCore
Definition qhash.h:844
int m_flags
Definition rcc.cpp:121
bool m_isEmpty
Definition rcc.cpp:133
int m_compressLevel
Definition rcc.cpp:130
QMultiHash< DeduplicationKey, RCCFileInfo * > DeduplicationMultiHash
Definition rcc.cpp:111
QFileInfo m_fileInfo
Definition rcc.cpp:125
@ CompressedZstd
Definition rcc.cpp:78
@ Compressed
Definition rcc.cpp:76
@ Directory
Definition rcc.cpp:77
@ NoFlags
Definition rcc.cpp:75
RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language, QLocale::Territory territory, uint flags, RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, int compressThreshold, bool noZstd, bool isEmpty)
Definition rcc.cpp:145
int m_compressThreshold
Definition rcc.cpp:131
RCCFileInfo()=default
RCCFileInfo & operator=(RCCFileInfo &&other)=delete
bool m_noZstd
Definition rcc.cpp:132
RCCFileInfo & operator=(const RCCFileInfo &)=delete
RCCFileInfo * m_parent
Definition rcc.cpp:126
qint64 m_childOffset
Definition rcc.cpp:137
RCCResourceLibrary::CompressionAlgorithm m_compressAlgo
Definition rcc.cpp:129
qint64 m_nameOffset
Definition rcc.cpp:135
qint64 writeDataName(RCCResourceLibrary &, qint64 offset)
Definition rcc.cpp:422
RCCFileInfo(RCCFileInfo &&)=default
QLocale::Language m_language
Definition rcc.cpp:122
QString m_name
Definition rcc.cpp:124
QString resourceName() const
Definition rcc.cpp:167
qint64 m_dataOffset
Definition rcc.cpp:136
~RCCFileInfo()
Definition rcc.cpp:162
void writeDataInfo(RCCResourceLibrary &lib)
Definition rcc.cpp:176
RCCFileInfo(const RCCFileInfo &)=delete
QLocale::Territory m_territory
Definition rcc.cpp:123
QMultiHash< QString, RCCFileInfo * > m_children
Definition rcc.cpp:127
qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, DeduplicationMultiHash &dedupByContent, QString *errorMessage)
Definition rcc.cpp:248
RCCResourceLibrary(quint8 formatVersion)
Definition rcc.cpp:495
ResourceDataFileMap resourceDataFileMap() const
Definition rcc.cpp:916
bool readFiles(bool listMode, QIODevice &errorDevice)
Definition rcc.cpp:837
bool verbose() const
Definition rcc.h:49
QStringList dataFiles() const
Definition rcc.cpp:878
int formatVersion() const
Definition rcc.h:84
static int parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg)
Definition rcc.cpp:947
bool output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice)
Definition rcc.cpp:973
QHash< QString, QString > ResourceDataFileMap
Definition rcc.h:45
#define qPrintable(string)
Definition qstring.h:1705
#define QStringLiteral(str)
Definition qstring.h:1847
static bool parseBoolean(QStringView value, QString *errorMsg)
Definition rcc.cpp:527
static const char hexDigits[]
Definition rcc.cpp:1051
RCCXmlTag
Definition rcc.cpp:520
@ RccTag
Definition rcc.cpp:521
@ ResourceTag
Definition rcc.cpp:522
@ FileTag
Definition rcc.cpp:523
@ CONSTANT_USENAMESPACE
Definition rcc.cpp:34
@ CONSTANT_ZSTDCOMPRESSLEVEL_CHECK
Definition rcc.cpp:36
@ CONSTANT_COMPRESSLEVEL_DEFAULT
Definition rcc.cpp:35
@ CONSTANT_ZSTDCOMPRESSLEVEL_STORE
Definition rcc.cpp:37
@ CONSTANT_COMPRESSTHRESHOLD_DEFAULT
Definition rcc.cpp:38
static QString msgOpenReadFailed(const QString &fname, const QString &why)
Definition rcc.cpp:57
Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE)
static size_t qHash(const RCCFileInfo::DeduplicationKey &key, size_t seed) noexcept
Definition rcc.cpp:140
static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m)
Definition rcc.cpp:901
bool operator==(const DeduplicationKey &other) const
Definition rcc.cpp:102
RCCResourceLibrary::CompressionAlgorithm compressAlgo
Definition rcc.cpp:97
result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const
Definition rcc.cpp:1317