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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtbase/src/tools/rcc/rcc.cpp