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 <qdiriterator.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 Designer (qttools/src/designer/src/lib/shared/rcc.cpp) |
25 | |
26 | QT_BEGIN_NAMESPACE |
27 | |
28 | using namespace Qt::StringLiterals; |
29 | |
30 | enum { |
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 | |
38 | void 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 | |
45 | void 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 | |
54 | static 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 | |
66 | class RCCFileInfo |
67 | { |
68 | public: |
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 | |
93 | public: |
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 | |
117 | RCCFileInfo::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 | |
134 | RCCFileInfo::~RCCFileInfo() |
135 | { |
136 | qDeleteAll(c: m_children); |
137 | } |
138 | |
139 | QString 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 | |
148 | void 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 | |
220 | qint64 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.absoluteFilePath().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 | |
364 | qint64 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 | |
423 | RCCResourceLibrary::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 | |
437 | RCCResourceLibrary::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 | |
460 | RCCResourceLibrary::~RCCResourceLibrary() |
461 | { |
462 | delete m_root; |
463 | #if QT_CONFIG(zstd) |
464 | ZSTD_freeCCtx(cctx: m_zstdCCtx); |
465 | #endif |
466 | } |
467 | |
468 | enum RCCXmlTag { |
469 | RccTag, |
470 | ResourceTag, |
471 | FileTag |
472 | }; |
473 | Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE); |
474 | |
475 | static 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 | |
486 | bool 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_RESOURCE) { |
516 | if (tokens.isEmpty() || tokens.top() != RccTag) { |
517 | reader.raiseError(message: "unexpected <RESOURCE> tag"_L1 ); |
518 | } else { |
519 | tokens.push(t: ResourceTag); |
520 | |
521 | QXmlStreamAttributes attributes = reader.attributes(); |
522 | language = QLocale::c().language(); |
523 | territory = QLocale::c().territory(); |
524 | |
525 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_LANG)) { |
526 | QString attribute = attributes.value(qualifiedName: m_strings.ATTRIBUTE_LANG).toString(); |
527 | QLocale lang = QLocale(attribute); |
528 | language = lang.language(); |
529 | if (2 == attribute.size()) { |
530 | // Language only |
531 | territory = QLocale::AnyTerritory; |
532 | } else { |
533 | territory = lang.territory(); |
534 | } |
535 | } |
536 | |
537 | prefix.clear(); |
538 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_PREFIX)) |
539 | prefix = attributes.value(qualifiedName: m_strings.ATTRIBUTE_PREFIX).toString(); |
540 | if (!prefix.startsWith(c: slash)) |
541 | prefix.prepend(c: slash); |
542 | if (!prefix.endsWith(c: slash)) |
543 | prefix += slash; |
544 | } |
545 | } else if (reader.name() == m_strings.TAG_FILE) { |
546 | if (tokens.isEmpty() || tokens.top() != ResourceTag) { |
547 | reader.raiseError(message: "unexpected <FILE> tag"_L1 ); |
548 | } else { |
549 | tokens.push(t: FileTag); |
550 | |
551 | QXmlStreamAttributes attributes = reader.attributes(); |
552 | alias.clear(); |
553 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_ALIAS)) |
554 | alias = attributes.value(qualifiedName: m_strings.ATTRIBUTE_ALIAS).toString(); |
555 | |
556 | compressAlgo = m_compressionAlgo; |
557 | compressLevel = m_compressLevel; |
558 | compressThreshold = m_compressThreshold; |
559 | |
560 | QString errorString; |
561 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_EMPTY)) |
562 | empty = parseBoolean(value: attributes.value(qualifiedName: m_strings.ATTRIBUTE_EMPTY), errorMsg: &errorString); |
563 | else |
564 | empty = false; |
565 | |
566 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_COMPRESSALGO)) |
567 | compressAlgo = parseCompressionAlgorithm(algo: attributes.value(qualifiedName: m_strings.ATTRIBUTE_COMPRESSALGO), errorMsg: &errorString); |
568 | if (errorString.isEmpty() && attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_COMPRESS)) { |
569 | QString value = attributes.value(qualifiedName: m_strings.ATTRIBUTE_COMPRESS).toString(); |
570 | compressLevel = parseCompressionLevel(algo: compressAlgo, level: value, errorMsg: &errorString); |
571 | } |
572 | |
573 | // Special case for -no-compress |
574 | if (m_compressLevel == -2) |
575 | compressAlgo = CompressionAlgorithm::None; |
576 | |
577 | if (attributes.hasAttribute(qualifiedName: m_strings.ATTRIBUTE_THRESHOLD)) |
578 | compressThreshold = attributes.value(qualifiedName: m_strings.ATTRIBUTE_THRESHOLD).toString().toInt(); |
579 | |
580 | if (!errorString.isEmpty()) |
581 | reader.raiseError(message: errorString); |
582 | } |
583 | } else { |
584 | reader.raiseError(message: "unexpected tag: %1"_L1 .arg(args: reader.name().toString())); |
585 | } |
586 | break; |
587 | |
588 | case QXmlStreamReader::EndElement: |
589 | if (reader.name() == m_strings.TAG_RCC) { |
590 | if (!tokens.isEmpty() && tokens.top() == RccTag) |
591 | tokens.pop(); |
592 | else |
593 | reader.raiseError(message: "unexpected closing tag"_L1 ); |
594 | } else if (reader.name() == m_strings.TAG_RESOURCE) { |
595 | if (!tokens.isEmpty() && tokens.top() == ResourceTag) |
596 | tokens.pop(); |
597 | else |
598 | reader.raiseError(message: "unexpected closing tag"_L1 ); |
599 | } else if (reader.name() == m_strings.TAG_FILE) { |
600 | if (!tokens.isEmpty() && tokens.top() == FileTag) |
601 | tokens.pop(); |
602 | else |
603 | reader.raiseError(message: "unexpected closing tag"_L1 ); |
604 | } |
605 | break; |
606 | |
607 | case QXmlStreamReader::Characters: |
608 | if (reader.isWhitespace()) |
609 | break; |
610 | if (tokens.isEmpty() || tokens.top() != FileTag) { |
611 | reader.raiseError(message: "unexpected text"_L1 ); |
612 | } else { |
613 | QString fileName = reader.text().toString(); |
614 | if (fileName.isEmpty()) { |
615 | const QString msg = QString::fromLatin1(ba: "RCC: Warning: Null node in XML of '%1'\n" ).arg(a: fname); |
616 | m_errorDevice->write(data: msg.toUtf8()); |
617 | } |
618 | |
619 | if (alias.isNull()) |
620 | alias = fileName; |
621 | |
622 | alias = QDir::cleanPath(path: alias); |
623 | while (alias.startsWith(s: "../"_L1 )) |
624 | alias.remove(i: 0, len: 3); |
625 | alias = QDir::cleanPath(path: m_resourceRoot) + prefix + alias; |
626 | |
627 | QString absFileName = fileName; |
628 | if (QDir::isRelativePath(path: absFileName)) |
629 | absFileName.prepend(s: currentPath); |
630 | QFileInfo file(absFileName); |
631 | if (file.isDir()) { |
632 | QDir dir(file.filePath()); |
633 | if (!alias.endsWith(c: slash)) |
634 | alias += slash; |
635 | |
636 | QStringList filePaths; |
637 | QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); |
638 | while (it.hasNext()) { |
639 | it.next(); |
640 | if (it.fileName() == "."_L1 || it.fileName() == ".."_L1 ) |
641 | continue; |
642 | filePaths.append(t: it.filePath()); |
643 | } |
644 | |
645 | // make rcc output deterministic |
646 | std::sort(first: filePaths.begin(), last: filePaths.end()); |
647 | |
648 | for (const QString &filePath : filePaths) { |
649 | QFileInfo child(filePath); |
650 | const bool arc = |
651 | addFile(alias: alias + child.fileName(), |
652 | file: RCCFileInfo(child.fileName(), child, language, territory, |
653 | child.isDir() ? RCCFileInfo::Directory |
654 | : RCCFileInfo::NoFlags, |
655 | compressAlgo, compressLevel, compressThreshold, |
656 | m_noZstd, empty)); |
657 | if (!arc) |
658 | m_failedResources.push_back(t: child.fileName()); |
659 | } |
660 | } else if (listMode || file.isFile()) { |
661 | const bool arc = |
662 | addFile(alias, |
663 | file: RCCFileInfo(alias.section(asep: slash, astart: -1), |
664 | file, |
665 | language, |
666 | territory, |
667 | RCCFileInfo::NoFlags, |
668 | compressAlgo, |
669 | compressLevel, |
670 | compressThreshold, |
671 | m_noZstd, empty) |
672 | ); |
673 | if (!arc) |
674 | m_failedResources.push_back(t: absFileName); |
675 | } else if (file.exists()) { |
676 | m_failedResources.push_back(t: absFileName); |
677 | const QString msg = QString::fromLatin1(ba: "RCC: Error in '%1': Entry '%2' is neither a file nor a directory\n" ) |
678 | .arg(args: fname, args&: fileName); |
679 | m_errorDevice->write(data: msg.toUtf8()); |
680 | return false; |
681 | } else { |
682 | m_failedResources.push_back(t: absFileName); |
683 | const QString msg = QString::fromLatin1(ba: "RCC: Error in '%1': Cannot find file '%2'\n" ) |
684 | .arg(args: fname, args&: fileName); |
685 | m_errorDevice->write(data: msg.toUtf8()); |
686 | return false; |
687 | } |
688 | } |
689 | break; |
690 | |
691 | default: |
692 | break; |
693 | } |
694 | } |
695 | |
696 | if (reader.hasError()) { |
697 | int errorLine = reader.lineNumber(); |
698 | int errorColumn = reader.columnNumber(); |
699 | QString errorMessage = reader.errorString(); |
700 | 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); |
701 | m_errorDevice->write(data: msg.toUtf8()); |
702 | return false; |
703 | } |
704 | |
705 | if (m_root == nullptr) { |
706 | const QString msg = QString::fromLatin1(ba: "RCC: Warning: No resources in '%1'.\n" ).arg(a: fname); |
707 | m_errorDevice->write(data: msg.toUtf8()); |
708 | if (!listMode && m_format == Binary) { |
709 | // create dummy entry, otherwise loading with QResource will crash |
710 | m_root = new RCCFileInfo{}; |
711 | m_root->m_flags = RCCFileInfo::Directory; |
712 | } |
713 | } |
714 | |
715 | return true; |
716 | } |
717 | |
718 | bool RCCResourceLibrary::addFile(const QString &alias, RCCFileInfo file) |
719 | { |
720 | Q_ASSERT(m_errorDevice); |
721 | if (file.m_fileInfo.size() > 0xffffffff) { |
722 | const QString msg = QString::fromLatin1(ba: "File too big: %1\n" ).arg(a: file.m_fileInfo.absoluteFilePath()); |
723 | m_errorDevice->write(data: msg.toUtf8()); |
724 | return false; |
725 | } |
726 | if (!m_root) { |
727 | m_root = new RCCFileInfo{}; |
728 | m_root->m_flags = RCCFileInfo::Directory; |
729 | } |
730 | |
731 | RCCFileInfo *parent = m_root; |
732 | const QStringList nodes = alias.split(sep: u'/'); |
733 | for (int i = 1; i < nodes.size()-1; ++i) { |
734 | const QString node = nodes.at(i); |
735 | if (node.isEmpty()) |
736 | continue; |
737 | if (!parent->m_children.contains(key: node)) { |
738 | RCCFileInfo *s = new RCCFileInfo{}; |
739 | s->m_name = node; |
740 | s->m_flags = RCCFileInfo::Directory; |
741 | s->m_parent = parent; |
742 | parent->m_children.insert(key: node, value: s); |
743 | parent = s; |
744 | } else { |
745 | parent = *parent->m_children.constFind(key: node); |
746 | } |
747 | } |
748 | |
749 | const QString filename = nodes.at(i: nodes.size()-1); |
750 | RCCFileInfo *s = new RCCFileInfo(std::move(file)); |
751 | s->m_parent = parent; |
752 | auto cbegin = parent->m_children.constFind(key: filename); |
753 | auto cend = parent->m_children.constEnd(); |
754 | for (auto it = cbegin; it != cend; ++it) { |
755 | if (it.key() == filename && it.value()->m_language == s->m_language && |
756 | it.value()->m_territory == s->m_territory) { |
757 | for (const QString &name : std::as_const(t&: m_fileNames)) { |
758 | qWarning(msg: "%s: Warning: potential duplicate alias detected: '%s'" , |
759 | qPrintable(name), qPrintable(filename)); |
760 | } |
761 | break; |
762 | } |
763 | } |
764 | parent->m_children.insert(key: filename, value: s); |
765 | return true; |
766 | } |
767 | |
768 | void RCCResourceLibrary::reset() |
769 | { |
770 | if (m_root) { |
771 | delete m_root; |
772 | m_root = nullptr; |
773 | } |
774 | m_errorDevice = nullptr; |
775 | m_failedResources.clear(); |
776 | } |
777 | |
778 | |
779 | bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice) |
780 | { |
781 | reset(); |
782 | m_errorDevice = &errorDevice; |
783 | //read in data |
784 | if (m_verbose) { |
785 | const QString msg = QString::fromLatin1(ba: "Processing %1 files [listMode=%2]\n" ) |
786 | .arg(a: m_fileNames.size()).arg(a: static_cast<int>(listMode)); |
787 | m_errorDevice->write(data: msg.toUtf8()); |
788 | } |
789 | for (int i = 0; i < m_fileNames.size(); ++i) { |
790 | QFile fileIn; |
791 | QString fname = m_fileNames.at(i); |
792 | QString pwd; |
793 | if (fname == "-"_L1 ) { |
794 | fname = "(stdin)"_L1 ; |
795 | pwd = QDir::currentPath(); |
796 | fileIn.setFileName(fname); |
797 | if (!fileIn.open(stdin, ioFlags: QIODevice::ReadOnly)) { |
798 | m_errorDevice->write(data: msgOpenReadFailed(fname, why: fileIn.errorString()).toUtf8()); |
799 | return false; |
800 | } |
801 | } else { |
802 | pwd = QFileInfo(fname).path(); |
803 | fileIn.setFileName(fname); |
804 | if (!fileIn.open(flags: QIODevice::ReadOnly)) { |
805 | m_errorDevice->write(data: msgOpenReadFailed(fname, why: fileIn.errorString()).toUtf8()); |
806 | return false; |
807 | } |
808 | } |
809 | if (m_verbose) { |
810 | const QString msg = QString::fromLatin1(ba: "Interpreting %1\n" ).arg(a: fname); |
811 | m_errorDevice->write(data: msg.toUtf8()); |
812 | } |
813 | |
814 | if (!interpretResourceFile(inputDevice: &fileIn, fname, currentPath: pwd, listMode)) |
815 | return false; |
816 | } |
817 | return true; |
818 | } |
819 | |
820 | QStringList RCCResourceLibrary::dataFiles() const |
821 | { |
822 | QStringList ret; |
823 | QStack<RCCFileInfo*> pending; |
824 | |
825 | if (!m_root) |
826 | return ret; |
827 | pending.push(t: m_root); |
828 | while (!pending.isEmpty()) { |
829 | RCCFileInfo *file = pending.pop(); |
830 | for (auto it = file->m_children.begin(); |
831 | it != file->m_children.end(); ++it) { |
832 | RCCFileInfo *child = it.value(); |
833 | if (child->m_flags & RCCFileInfo::Directory) |
834 | pending.push(t: child); |
835 | else |
836 | ret.append(t: child->m_fileInfo.filePath()); |
837 | } |
838 | } |
839 | return ret; |
840 | } |
841 | |
842 | // Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion |
843 | static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) |
844 | { |
845 | const QChar slash = u'/'; |
846 | const auto cend = m_root->m_children.constEnd(); |
847 | for (auto it = m_root->m_children.constBegin(); it != cend; ++it) { |
848 | const RCCFileInfo *child = it.value(); |
849 | const QString childName = path + slash + child->m_name; |
850 | if (child->m_flags & RCCFileInfo::Directory) { |
851 | resourceDataFileMapRecursion(m_root: child, path: childName, m); |
852 | } else { |
853 | m.insert(key: childName, value: child->m_fileInfo.filePath()); |
854 | } |
855 | } |
856 | } |
857 | |
858 | RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const |
859 | { |
860 | ResourceDataFileMap rc; |
861 | if (m_root) |
862 | resourceDataFileMapRecursion(m_root, path: QString(u':'), m&: rc); |
863 | return rc; |
864 | } |
865 | |
866 | RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg) |
867 | { |
868 | if (value == "best"_L1 ) |
869 | return CompressionAlgorithm::Best; |
870 | if (value == "zlib"_L1 ) { |
871 | #ifdef QT_NO_COMPRESS |
872 | *errorMsg = "zlib support not compiled in"_L1 ; |
873 | #else |
874 | return CompressionAlgorithm::Zlib; |
875 | #endif |
876 | } else if (value == "zstd"_L1 ) { |
877 | #if QT_CONFIG(zstd) |
878 | return CompressionAlgorithm::Zstd; |
879 | #else |
880 | *errorMsg = "Zstandard support not compiled in"_L1 ; |
881 | #endif |
882 | } else if (value != "none"_L1 ) { |
883 | *errorMsg = QString::fromLatin1(ba: "Unknown compression algorithm '%1'" ).arg(a: value); |
884 | } |
885 | |
886 | return CompressionAlgorithm::None; |
887 | } |
888 | |
889 | int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg) |
890 | { |
891 | bool ok; |
892 | int c = level.toInt(ok: &ok); |
893 | if (ok) { |
894 | switch (algo) { |
895 | case CompressionAlgorithm::None: |
896 | case CompressionAlgorithm::Best: |
897 | return 0; |
898 | case CompressionAlgorithm::Zlib: |
899 | if (c >= 1 && c <= 9) |
900 | return c; |
901 | break; |
902 | case CompressionAlgorithm::Zstd: |
903 | #if QT_CONFIG(zstd) |
904 | if (c >= 0 && c <= ZSTD_maxCLevel()) |
905 | return c; |
906 | #endif |
907 | break; |
908 | } |
909 | } |
910 | |
911 | *errorMsg = QString::fromLatin1(ba: "invalid compression level '%1'" ).arg(a: level); |
912 | return 0; |
913 | } |
914 | |
915 | bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice) |
916 | { |
917 | m_errorDevice = &errorDevice; |
918 | |
919 | if (m_format == Pass2) { |
920 | const char pattern[] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' }; |
921 | bool foundSignature = false; |
922 | |
923 | while (true) { |
924 | char c; |
925 | for (int i = 0; i < 8; ) { |
926 | if (!tempDevice.getChar(c: &c)) { |
927 | if (foundSignature) |
928 | return true; |
929 | m_errorDevice->write(data: "No data signature found\n" ); |
930 | return false; |
931 | } |
932 | if (c == pattern[i]) { |
933 | ++i; |
934 | } else { |
935 | for (int k = 0; k < i; ++k) |
936 | outDevice.putChar(c: pattern[k]); |
937 | outDevice.putChar(c); |
938 | i = 0; |
939 | } |
940 | } |
941 | |
942 | m_outDevice = &outDevice; |
943 | quint64 start = outDevice.pos(); |
944 | writeDataBlobs(); |
945 | quint64 len = outDevice.pos() - start; |
946 | |
947 | tempDevice.seek(pos: tempDevice.pos() + len - 8); |
948 | foundSignature = true; |
949 | } |
950 | } |
951 | |
952 | //write out |
953 | if (m_verbose) |
954 | m_errorDevice->write(data: "Outputting code\n" ); |
955 | if (!writeHeader()) { |
956 | m_errorDevice->write(data: "Could not write header\n" ); |
957 | return false; |
958 | } |
959 | if (m_root) { |
960 | if (!writeDataBlobs()) { |
961 | m_errorDevice->write(data: "Could not write data blobs.\n" ); |
962 | return false; |
963 | } |
964 | if (!writeDataNames()) { |
965 | m_errorDevice->write(data: "Could not write file names\n" ); |
966 | return false; |
967 | } |
968 | if (!writeDataStructure()) { |
969 | m_errorDevice->write(data: "Could not write data tree\n" ); |
970 | return false; |
971 | } |
972 | } |
973 | if (!writeInitializer()) { |
974 | m_errorDevice->write(data: "Could not write footer\n" ); |
975 | return false; |
976 | } |
977 | outDevice.write(data: m_out.constData(), len: m_out.size()); |
978 | return true; |
979 | } |
980 | |
981 | void RCCResourceLibrary::writeDecimal(int value) |
982 | { |
983 | Q_ASSERT(m_format != RCCResourceLibrary::Binary); |
984 | char buf[std::numeric_limits<int>::digits10 + 2]; |
985 | int n = snprintf(s: buf, maxlen: sizeof(buf), format: "%d" , value); |
986 | write(str: buf, len: n); |
987 | } |
988 | |
989 | static const char hexDigits[] = "0123456789abcdef" ; |
990 | |
991 | inline void RCCResourceLibrary::write2HexDigits(quint8 number) |
992 | { |
993 | writeChar(c: hexDigits[number >> 4]); |
994 | writeChar(c: hexDigits[number & 0xf]); |
995 | } |
996 | |
997 | void RCCResourceLibrary::writeHex(quint8 tmp) |
998 | { |
999 | switch (m_format) { |
1000 | case RCCResourceLibrary::Python_Code: |
1001 | if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') { |
1002 | writeChar(c: char(tmp)); |
1003 | } else { |
1004 | writeChar(c: '\\'); |
1005 | writeChar(c: 'x'); |
1006 | write2HexDigits(number: tmp); |
1007 | } |
1008 | break; |
1009 | default: |
1010 | writeChar(c: '0'); |
1011 | writeChar(c: 'x'); |
1012 | if (tmp < 16) |
1013 | writeChar(c: hexDigits[tmp]); |
1014 | else |
1015 | write2HexDigits(number: tmp); |
1016 | writeChar(c: ','); |
1017 | break; |
1018 | } |
1019 | } |
1020 | |
1021 | void RCCResourceLibrary::writeNumber2(quint16 number) |
1022 | { |
1023 | if (m_format == RCCResourceLibrary::Binary) { |
1024 | writeChar(c: number >> 8); |
1025 | writeChar(c: number); |
1026 | } else { |
1027 | writeHex(tmp: number >> 8); |
1028 | writeHex(tmp: number); |
1029 | } |
1030 | } |
1031 | |
1032 | void RCCResourceLibrary::writeNumber4(quint32 number) |
1033 | { |
1034 | if (m_format == RCCResourceLibrary::Pass2) { |
1035 | m_outDevice->putChar(c: char(number >> 24)); |
1036 | m_outDevice->putChar(c: char(number >> 16)); |
1037 | m_outDevice->putChar(c: char(number >> 8)); |
1038 | m_outDevice->putChar(c: char(number)); |
1039 | } else if (m_format == RCCResourceLibrary::Binary) { |
1040 | writeChar(c: number >> 24); |
1041 | writeChar(c: number >> 16); |
1042 | writeChar(c: number >> 8); |
1043 | writeChar(c: number); |
1044 | } else { |
1045 | writeHex(tmp: number >> 24); |
1046 | writeHex(tmp: number >> 16); |
1047 | writeHex(tmp: number >> 8); |
1048 | writeHex(tmp: number); |
1049 | } |
1050 | } |
1051 | |
1052 | void RCCResourceLibrary::writeNumber8(quint64 number) |
1053 | { |
1054 | if (m_format == RCCResourceLibrary::Pass2) { |
1055 | m_outDevice->putChar(c: char(number >> 56)); |
1056 | m_outDevice->putChar(c: char(number >> 48)); |
1057 | m_outDevice->putChar(c: char(number >> 40)); |
1058 | m_outDevice->putChar(c: char(number >> 32)); |
1059 | m_outDevice->putChar(c: char(number >> 24)); |
1060 | m_outDevice->putChar(c: char(number >> 16)); |
1061 | m_outDevice->putChar(c: char(number >> 8)); |
1062 | m_outDevice->putChar(c: char(number)); |
1063 | } else if (m_format == RCCResourceLibrary::Binary) { |
1064 | writeChar(c: number >> 56); |
1065 | writeChar(c: number >> 48); |
1066 | writeChar(c: number >> 40); |
1067 | writeChar(c: number >> 32); |
1068 | writeChar(c: number >> 24); |
1069 | writeChar(c: number >> 16); |
1070 | writeChar(c: number >> 8); |
1071 | writeChar(c: number); |
1072 | } else { |
1073 | writeHex(tmp: number >> 56); |
1074 | writeHex(tmp: number >> 48); |
1075 | writeHex(tmp: number >> 40); |
1076 | writeHex(tmp: number >> 32); |
1077 | writeHex(tmp: number >> 24); |
1078 | writeHex(tmp: number >> 16); |
1079 | writeHex(tmp: number >> 8); |
1080 | writeHex(tmp: number); |
1081 | } |
1082 | } |
1083 | |
1084 | bool RCCResourceLibrary::() |
1085 | { |
1086 | switch (m_format) { |
1087 | case C_Code: |
1088 | case Pass1: |
1089 | writeString(s: "/****************************************************************************\n" ); |
1090 | writeString(s: "** Resource object code\n" ); |
1091 | writeString(s: "**\n" ); |
1092 | writeString(s: "** Created by: The Resource Compiler for Qt version " ); |
1093 | writeByteArray(QT_VERSION_STR); |
1094 | writeString(s: "\n**\n" ); |
1095 | writeString(s: "** WARNING! All changes made in this file will be lost!\n" ); |
1096 | writeString( s: "*****************************************************************************/\n\n" ); |
1097 | break; |
1098 | case Python_Code: |
1099 | writeString(s: "# Resource object code (Python 3)\n" ); |
1100 | writeString(s: "# Created by: object code\n" ); |
1101 | writeString(s: "# Created by: The Resource Compiler for Qt version " ); |
1102 | writeByteArray(QT_VERSION_STR); |
1103 | writeString(s: "\n" ); |
1104 | writeString(s: "# WARNING! All changes made in this file will be lost!\n\n" ); |
1105 | writeString(s: "from PySide" ); |
1106 | writeByteArray(other: QByteArray::number(QT_VERSION_MAJOR)); |
1107 | writeString(s: " import QtCore\n\n" ); |
1108 | break; |
1109 | case Binary: |
1110 | writeString(s: "qres" ); |
1111 | writeNumber4(number: 0); |
1112 | writeNumber4(number: 0); |
1113 | writeNumber4(number: 0); |
1114 | writeNumber4(number: 0); |
1115 | if (m_formatVersion >= 3) |
1116 | writeNumber4(number: m_overallFlags); |
1117 | break; |
1118 | default: |
1119 | break; |
1120 | } |
1121 | return true; |
1122 | } |
1123 | |
1124 | bool RCCResourceLibrary::writeDataBlobs() |
1125 | { |
1126 | Q_ASSERT(m_errorDevice); |
1127 | switch (m_format) { |
1128 | case C_Code: |
1129 | writeString(s: "static const unsigned char qt_resource_data[] = {\n" ); |
1130 | break; |
1131 | case Python_Code: |
1132 | writeString(s: "qt_resource_data = b\"\\\n" ); |
1133 | break; |
1134 | case Binary: |
1135 | m_dataOffset = m_out.size(); |
1136 | break; |
1137 | default: |
1138 | break; |
1139 | } |
1140 | |
1141 | if (!m_root) |
1142 | return false; |
1143 | |
1144 | QStack<RCCFileInfo*> pending; |
1145 | pending.push(t: m_root); |
1146 | qint64 offset = 0; |
1147 | QString errorMessage; |
1148 | while (!pending.isEmpty()) { |
1149 | RCCFileInfo *file = pending.pop(); |
1150 | for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) { |
1151 | RCCFileInfo *child = it.value(); |
1152 | if (child->m_flags & RCCFileInfo::Directory) |
1153 | pending.push(t: child); |
1154 | else { |
1155 | offset = child->writeDataBlob(lib&: *this, offset, errorMessage: &errorMessage); |
1156 | if (offset == 0) { |
1157 | m_errorDevice->write(data: errorMessage.toUtf8()); |
1158 | return false; |
1159 | } |
1160 | } |
1161 | } |
1162 | } |
1163 | switch (m_format) { |
1164 | case C_Code: |
1165 | writeString(s: "\n};\n\n" ); |
1166 | break; |
1167 | case Python_Code: |
1168 | writeString(s: "\"\n\n" ); |
1169 | break; |
1170 | case Pass1: |
1171 | if (offset < 8) |
1172 | offset = 8; |
1173 | writeString(s: "\nstatic const unsigned char qt_resource_data[" ); |
1174 | writeByteArray(other: QByteArray::number(offset)); |
1175 | writeString(s: "] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n" ); |
1176 | break; |
1177 | default: |
1178 | break; |
1179 | } |
1180 | return true; |
1181 | } |
1182 | |
1183 | bool RCCResourceLibrary::writeDataNames() |
1184 | { |
1185 | switch (m_format) { |
1186 | case C_Code: |
1187 | case Pass1: |
1188 | writeString(s: "static const unsigned char qt_resource_name[] = {\n" ); |
1189 | break; |
1190 | case Python_Code: |
1191 | writeString(s: "qt_resource_name = b\"\\\n" ); |
1192 | break; |
1193 | case Binary: |
1194 | m_namesOffset = m_out.size(); |
1195 | break; |
1196 | default: |
1197 | break; |
1198 | } |
1199 | |
1200 | QHash<QString, int> names; |
1201 | QStack<RCCFileInfo*> pending; |
1202 | |
1203 | if (!m_root) |
1204 | return false; |
1205 | |
1206 | pending.push(t: m_root); |
1207 | qint64 offset = 0; |
1208 | while (!pending.isEmpty()) { |
1209 | RCCFileInfo *file = pending.pop(); |
1210 | for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) { |
1211 | RCCFileInfo *child = it.value(); |
1212 | if (child->m_flags & RCCFileInfo::Directory) |
1213 | pending.push(t: child); |
1214 | if (names.contains(key: child->m_name)) { |
1215 | child->m_nameOffset = names.value(key: child->m_name); |
1216 | } else { |
1217 | names.insert(key: child->m_name, value: offset); |
1218 | offset = child->writeDataName(lib&: *this, offset); |
1219 | } |
1220 | } |
1221 | } |
1222 | switch (m_format) { |
1223 | case C_Code: |
1224 | case Pass1: |
1225 | writeString(s: "\n};\n\n" ); |
1226 | break; |
1227 | case Python_Code: |
1228 | writeString(s: "\"\n\n" ); |
1229 | break; |
1230 | default: |
1231 | break; |
1232 | } |
1233 | return true; |
1234 | } |
1235 | |
1236 | struct qt_rcc_compare_hash |
1237 | { |
1238 | typedef bool result_type; |
1239 | result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const |
1240 | { |
1241 | return qt_hash(key: left->m_name) < qt_hash(key: right->m_name); |
1242 | } |
1243 | }; |
1244 | |
1245 | bool RCCResourceLibrary::writeDataStructure() |
1246 | { |
1247 | switch (m_format) { |
1248 | case C_Code: |
1249 | case Pass1: |
1250 | writeString(s: "static const unsigned char qt_resource_struct[] = {\n" ); |
1251 | break; |
1252 | case Python_Code: |
1253 | writeString(s: "qt_resource_struct = b\"\\\n" ); |
1254 | break; |
1255 | case Binary: |
1256 | m_treeOffset = m_out.size(); |
1257 | break; |
1258 | default: |
1259 | break; |
1260 | } |
1261 | |
1262 | QStack<RCCFileInfo*> pending; |
1263 | |
1264 | if (!m_root) |
1265 | return false; |
1266 | |
1267 | //calculate the child offsets (flat) |
1268 | pending.push(t: m_root); |
1269 | int offset = 1; |
1270 | while (!pending.isEmpty()) { |
1271 | RCCFileInfo *file = pending.pop(); |
1272 | file->m_childOffset = offset; |
1273 | |
1274 | //sort by hash value for binary lookup |
1275 | QList<RCCFileInfo*> m_children = file->m_children.values(); |
1276 | std::sort(first: m_children.begin(), last: m_children.end(), comp: qt_rcc_compare_hash()); |
1277 | |
1278 | //write out the actual data now |
1279 | for (int i = 0; i < m_children.size(); ++i) { |
1280 | RCCFileInfo *child = m_children.at(i); |
1281 | ++offset; |
1282 | if (child->m_flags & RCCFileInfo::Directory) |
1283 | pending.push(t: child); |
1284 | } |
1285 | } |
1286 | |
1287 | //write out the structure (ie iterate again!) |
1288 | pending.push(t: m_root); |
1289 | m_root->writeDataInfo(lib&: *this); |
1290 | while (!pending.isEmpty()) { |
1291 | RCCFileInfo *file = pending.pop(); |
1292 | |
1293 | //sort by hash value for binary lookup |
1294 | QList<RCCFileInfo*> m_children = file->m_children.values(); |
1295 | std::sort(first: m_children.begin(), last: m_children.end(), comp: qt_rcc_compare_hash()); |
1296 | |
1297 | //write out the actual data now |
1298 | for (int i = 0; i < m_children.size(); ++i) { |
1299 | RCCFileInfo *child = m_children.at(i); |
1300 | child->writeDataInfo(lib&: *this); |
1301 | if (child->m_flags & RCCFileInfo::Directory) |
1302 | pending.push(t: child); |
1303 | } |
1304 | } |
1305 | switch (m_format) { |
1306 | case C_Code: |
1307 | case Pass1: |
1308 | writeString(s: "\n};\n\n" ); |
1309 | break; |
1310 | case Python_Code: |
1311 | writeString(s: "\"\n\n" ); |
1312 | break; |
1313 | default: |
1314 | break; |
1315 | } |
1316 | |
1317 | return true; |
1318 | } |
1319 | |
1320 | void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name) |
1321 | { |
1322 | if (m_useNameSpace) { |
1323 | writeString(s: "QT_RCC_MANGLE_NAMESPACE(" ); |
1324 | writeByteArray(other: name); |
1325 | writeChar(c: ')'); |
1326 | } else { |
1327 | writeByteArray(other: name); |
1328 | } |
1329 | } |
1330 | |
1331 | void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name) |
1332 | { |
1333 | if (m_useNameSpace) { |
1334 | writeString(s: "QT_RCC_PREPEND_NAMESPACE(" ); |
1335 | writeByteArray(other: name); |
1336 | writeChar(c: ')'); |
1337 | } else { |
1338 | writeByteArray(other: name); |
1339 | } |
1340 | } |
1341 | |
1342 | bool RCCResourceLibrary::writeInitializer() |
1343 | { |
1344 | if (m_format == C_Code || m_format == Pass1) { |
1345 | //write("\nQT_BEGIN_NAMESPACE\n"); |
1346 | QString initNameStr = m_initName; |
1347 | if (!initNameStr.isEmpty()) { |
1348 | initNameStr.prepend(c: u'_'); |
1349 | auto isAsciiLetterOrNumber = [] (QChar c) -> bool { |
1350 | ushort ch = c.unicode(); |
1351 | return (ch >= '0' && ch <= '9') || |
1352 | (ch >= 'A' && ch <= 'Z') || |
1353 | (ch >= 'a' && ch <= 'z') || |
1354 | ch == '_'; |
1355 | }; |
1356 | for (QChar &c : initNameStr) { |
1357 | if (!isAsciiLetterOrNumber(c)) |
1358 | c = u'_'; |
1359 | } |
1360 | } |
1361 | QByteArray initName = initNameStr.toLatin1(); |
1362 | |
1363 | //init |
1364 | if (m_useNameSpace) { |
1365 | writeString(s: "#ifdef QT_NAMESPACE\n" |
1366 | "# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name\n" |
1367 | "# define QT_RCC_MANGLE_NAMESPACE0(x) x\n" |
1368 | "# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b\n" |
1369 | "# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)\n" |
1370 | "# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \\\n" |
1371 | " QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))\n" |
1372 | "#else\n" |
1373 | "# define QT_RCC_PREPEND_NAMESPACE(name) name\n" |
1374 | "# define QT_RCC_MANGLE_NAMESPACE(name) name\n" |
1375 | "#endif\n\n" ); |
1376 | |
1377 | writeString(s: "#ifdef QT_NAMESPACE\n" |
1378 | "namespace QT_NAMESPACE {\n" |
1379 | "#endif\n\n" ); |
1380 | } |
1381 | |
1382 | if (m_root) { |
1383 | writeString(s: "bool qRegisterResourceData" |
1384 | "(int, const unsigned char *, " |
1385 | "const unsigned char *, const unsigned char *);\n" ); |
1386 | writeString(s: "bool qUnregisterResourceData" |
1387 | "(int, const unsigned char *, " |
1388 | "const unsigned char *, const unsigned char *);\n\n" ); |
1389 | |
1390 | if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) { |
1391 | // use variable relocations with ELF and Mach-O |
1392 | writeString(s: "#if defined(__ELF__) || defined(__APPLE__)\n" ); |
1393 | if (m_overallFlags & RCCFileInfo::Compressed) { |
1394 | writeString(s: "static inline unsigned char qResourceFeatureZlib()\n" |
1395 | "{\n" |
1396 | " extern const unsigned char qt_resourceFeatureZlib;\n" |
1397 | " return qt_resourceFeatureZlib;\n" |
1398 | "}\n" ); |
1399 | } |
1400 | if (m_overallFlags & RCCFileInfo::CompressedZstd) { |
1401 | writeString(s: "static inline unsigned char qResourceFeatureZstd()\n" |
1402 | "{\n" |
1403 | " extern const unsigned char qt_resourceFeatureZstd;\n" |
1404 | " return qt_resourceFeatureZstd;\n" |
1405 | "}\n" ); |
1406 | } |
1407 | writeString(s: "#else\n" ); |
1408 | if (m_overallFlags & RCCFileInfo::Compressed) |
1409 | writeString(s: "unsigned char qResourceFeatureZlib();\n" ); |
1410 | if (m_overallFlags & RCCFileInfo::CompressedZstd) |
1411 | writeString(s: "unsigned char qResourceFeatureZstd();\n" ); |
1412 | writeString(s: "#endif\n\n" ); |
1413 | } |
1414 | } |
1415 | |
1416 | if (m_useNameSpace) |
1417 | writeString(s: "#ifdef QT_NAMESPACE\n}\n#endif\n\n" ); |
1418 | |
1419 | QByteArray initResources = "qInitResources" ; |
1420 | initResources += initName; |
1421 | |
1422 | // Work around -Wmissing-declarations warnings. |
1423 | writeString(s: "int " ); |
1424 | writeMangleNamespaceFunction(name: initResources); |
1425 | writeString(s: "();\n" ); |
1426 | |
1427 | writeString(s: "int " ); |
1428 | writeMangleNamespaceFunction(name: initResources); |
1429 | writeString(s: "()\n{\n" ); |
1430 | |
1431 | if (m_root) { |
1432 | writeString(s: " int version = " ); |
1433 | writeDecimal(value: m_formatVersion); |
1434 | writeString(s: ";\n " ); |
1435 | writeAddNamespaceFunction(name: "qRegisterResourceData" ); |
1436 | writeString(s: "\n (version, qt_resource_struct, " |
1437 | "qt_resource_name, qt_resource_data);\n" ); |
1438 | } |
1439 | writeString(s: " return 1;\n" ); |
1440 | writeString(s: "}\n\n" ); |
1441 | |
1442 | //cleanup |
1443 | QByteArray cleanResources = "qCleanupResources" ; |
1444 | cleanResources += initName; |
1445 | |
1446 | // Work around -Wmissing-declarations warnings. |
1447 | writeString(s: "int " ); |
1448 | writeMangleNamespaceFunction(name: cleanResources); |
1449 | writeString(s: "();\n" ); |
1450 | |
1451 | writeString(s: "int " ); |
1452 | writeMangleNamespaceFunction(name: cleanResources); |
1453 | writeString(s: "()\n{\n" ); |
1454 | if (m_root) { |
1455 | writeString(s: " int version = " ); |
1456 | writeDecimal(value: m_formatVersion); |
1457 | writeString(s: ";\n " ); |
1458 | |
1459 | // ODR-use certain symbols from QtCore if we require optional features |
1460 | if (m_overallFlags & RCCFileInfo::Compressed) { |
1461 | writeString(s: "version += " ); |
1462 | writeAddNamespaceFunction(name: "qResourceFeatureZlib()" ); |
1463 | writeString(s: ";\n " ); |
1464 | } |
1465 | if (m_overallFlags & RCCFileInfo::CompressedZstd) { |
1466 | writeString(s: "version += " ); |
1467 | writeAddNamespaceFunction(name: "qResourceFeatureZstd()" ); |
1468 | writeString(s: ";\n " ); |
1469 | } |
1470 | |
1471 | writeAddNamespaceFunction(name: "qUnregisterResourceData" ); |
1472 | writeString(s: "\n (version, qt_resource_struct, " |
1473 | "qt_resource_name, qt_resource_data);\n" ); |
1474 | } |
1475 | writeString(s: " return 1;\n" ); |
1476 | writeString(s: "}\n\n" ); |
1477 | |
1478 | // -Wexit-time-destructors was added to clang 3.0.0 in 2011. |
1479 | writeString(s: "#ifdef __clang__\n" |
1480 | "# pragma clang diagnostic push\n" |
1481 | "# pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n" |
1482 | "#endif\n\n" ); |
1483 | |
1484 | writeString(s: "namespace {\n" |
1485 | " struct initializer {\n" ); |
1486 | |
1487 | if (m_useNameSpace) { |
1488 | writeByteArray(other: " initializer() { QT_RCC_MANGLE_NAMESPACE(" + initResources + ")(); }\n" |
1489 | " ~initializer() { QT_RCC_MANGLE_NAMESPACE(" + cleanResources + ")(); }\n" ); |
1490 | } else { |
1491 | writeByteArray(other: " initializer() { " + initResources + "(); }\n" |
1492 | " ~initializer() { " + cleanResources + "(); }\n" ); |
1493 | } |
1494 | writeString(s: " } dummy;\n" |
1495 | "}\n\n" ); |
1496 | |
1497 | writeString(s: "#ifdef __clang__\n" |
1498 | "# pragma clang diagnostic pop\n" |
1499 | "#endif\n" ); |
1500 | |
1501 | |
1502 | } else if (m_format == Binary) { |
1503 | int i = 4; |
1504 | char *p = m_out.data(); |
1505 | p[i++] = 0; |
1506 | p[i++] = 0; |
1507 | p[i++] = 0; |
1508 | p[i++] = m_formatVersion; |
1509 | |
1510 | p[i++] = (m_treeOffset >> 24) & 0xff; |
1511 | p[i++] = (m_treeOffset >> 16) & 0xff; |
1512 | p[i++] = (m_treeOffset >> 8) & 0xff; |
1513 | p[i++] = (m_treeOffset >> 0) & 0xff; |
1514 | |
1515 | p[i++] = (m_dataOffset >> 24) & 0xff; |
1516 | p[i++] = (m_dataOffset >> 16) & 0xff; |
1517 | p[i++] = (m_dataOffset >> 8) & 0xff; |
1518 | p[i++] = (m_dataOffset >> 0) & 0xff; |
1519 | |
1520 | p[i++] = (m_namesOffset >> 24) & 0xff; |
1521 | p[i++] = (m_namesOffset >> 16) & 0xff; |
1522 | p[i++] = (m_namesOffset >> 8) & 0xff; |
1523 | p[i++] = (m_namesOffset >> 0) & 0xff; |
1524 | |
1525 | if (m_formatVersion >= 3) { |
1526 | p[i++] = (m_overallFlags >> 24) & 0xff; |
1527 | p[i++] = (m_overallFlags >> 16) & 0xff; |
1528 | p[i++] = (m_overallFlags >> 8) & 0xff; |
1529 | p[i++] = (m_overallFlags >> 0) & 0xff; |
1530 | } |
1531 | } else if (m_format == Python_Code) { |
1532 | writeString(s: "def qInitResources():\n" ); |
1533 | writeString(s: " QtCore.qRegisterResourceData(0x" ); |
1534 | write2HexDigits(number: m_formatVersion); |
1535 | writeString(s: ", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n" ); |
1536 | writeString(s: "def qCleanupResources():\n" ); |
1537 | writeString(s: " QtCore.qUnregisterResourceData(0x" ); |
1538 | write2HexDigits(number: m_formatVersion); |
1539 | writeString(s: ", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n" ); |
1540 | writeString(s: "qInitResources()\n" ); |
1541 | } |
1542 | return true; |
1543 | } |
1544 | |
1545 | QT_END_NAMESPACE |
1546 | |