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