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 | |
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.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 | |
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_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 | |
724 | bool 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 | |
774 | void 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 | |
785 | bool 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 | |
826 | QStringList 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 |
849 | static 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 | |
864 | RCCResourceLibrary::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 | |
872 | RCCResourceLibrary::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 | |
895 | int 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 | |
921 | bool 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 | |
991 | void 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 | |
999 | static const char hexDigits[] = "0123456789abcdef"; |
1000 | |
1001 | inline void RCCResourceLibrary::write2HexDigits(quint8 number) |
1002 | { |
1003 | writeChar(c: hexDigits[number >> 4]); |
1004 | writeChar(c: hexDigits[number & 0xf]); |
1005 | } |
1006 | |
1007 | void 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 | |
1031 | void 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 | |
1042 | void 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 | |
1062 | void 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 | |
1094 | bool 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 | |
1148 | bool 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 | |
1207 | bool 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 | |
1260 | struct 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 | |
1269 | bool 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 | |
1344 | void 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 | |
1355 | void 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 | |
1366 | bool 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 | |
1571 | QT_END_NAMESPACE |
1572 |
Definitions
- write
- writeByteArray
- msgOpenReadFailed
- RCCFileInfo
- Flags
- RCCFileInfo
- RCCFileInfo
- operator=
- RCCFileInfo
- operator=
- RCCFileInfo
- ~RCCFileInfo
- resourceName
- writeDataInfo
- writeDataBlob
- writeDataName
- Strings
- RCCResourceLibrary
- ~RCCResourceLibrary
- RCCXmlTag
- parseBoolean
- interpretResourceFile
- addFile
- reset
- readFiles
- dataFiles
- resourceDataFileMapRecursion
- resourceDataFileMap
- parseCompressionAlgorithm
- parseCompressionLevel
- output
- writeDecimal
- hexDigits
- write2HexDigits
- writeHex
- writeNumber2
- writeNumber4
- writeNumber8
- writeHeader
- writeDataBlobs
- writeDataNames
- qt_rcc_compare_hash
- operator()
- writeDataStructure
- writeMangleNamespaceFunction
- writeAddNamespaceFunction
Start learning QML with our Intro Training
Find out more