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